diff --git a/.circleci/config.yml b/.circleci/config.yml index 1067eb9d9..28c6e8c90 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -211,12 +211,23 @@ jobs: key: cache-pre-commit-<>-{{ checksum "<>" }} paths: - ~/.cache/pre-commit + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-{{ checksum "yarn.lock" }} + - run: + name: Install Dependencies + command: yarn + + - save_cache: + name: Save Yarn Package Cache + key: yarn-packages-{{ checksum "yarn.lock" }} + paths: + - .yarn/cache + - .yarn/unplugged - run: name: Run pre-commit - command: pre-commit run --all-files --config <> - - run: - name: Run deployment pre-commit - command: pre-commit run --all-files --config <> + command: ./.husky/pre-commit - run: command: git --no-pager diff name: git diff @@ -248,6 +259,7 @@ jobs: S3_SECRET_KEY: 'minioadmin' S3_BUCKET: 'speckle-server' S3_CREATE_BUCKET: 'true' + S3_REGION: '' # optional, defaults to 'us-east-1' steps: - checkout - restore_cache: diff --git a/.github/PULL_REQUEST_TEMPLATE/PR_TEMPLATE.md b/.github/pull_request_template.md similarity index 74% rename from .github/PULL_REQUEST_TEMPLATE/PR_TEMPLATE.md rename to .github/pull_request_template.md index 637e5f616..1efeba6a8 100644 --- a/.github/PULL_REQUEST_TEMPLATE/PR_TEMPLATE.md +++ b/.github/pull_request_template.md @@ -34,7 +34,7 @@ Connects #123 --> -## To-do before merge +## To-do before merge: @@ -84,3 +84,19 @@ addressed, and remove any items that are not relevant to this PR. - [ ] My code follows a similar style to existing code. - [ ] I have added appropriate tests. - [ ] I have updated or added relevant documentation. + +## References + + diff --git a/.husky/pre-commit b/.husky/pre-commit index 1363567bd..8cddd4ca6 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,16 +1,32 @@ -#!/usr/bin/env bash -set -eo pipefail -[ -n "$CI" ] && exit 0 +#!/usr/bin/env sh +# shellcheck disable=SC1091 +set -e -#shellcheck source=/dev/null -. "$(dirname "$0")/_/husky.sh" +if [ -n "$CI" ] +then + echo "running eslint" + yarn lint + yarn prettier:check +else +# shellcheck disable=SC1090 + . "$(dirname "$0")/_/husky.sh" + yarn lint-staged +fi -yarn lint-staged -if ! command -v pre-commit &> /dev/null; then exit 0; fi -echo "πŸ” Detected pre-commit on this system. Running pre-commit checks..." -pre-commit run --all-files --config .pre-commit-config.yaml +echo "πŸ” looking for additional linter dependencies" -if ! command -v hadolint &> /dev/null || ! command -v helm &> /dev/null || ! command -v shellcheck &> /dev/null; then exit 0; fi -echo "πŸ” Detected additional dependencies (hadolint, helm, and shellcheck) on this system. Running additional pre-commit checks..." -pre-commit run --all-files --config .pre-commit-config.deployment.yaml +check_dependencies_available() { + for i in "${@}" + do + if ! command -v "${i}"; then + echo "No ${i} executable found skipping additional checks" >&2 + exit 0 + fi + done +} + +check_dependencies_available pre-commit hadolint helm shellcheck + +echo "All systems functional, running additional pre-commit checks..." +pre-commit run --all-files diff --git a/.pre-commit-config.deployment.yaml b/.pre-commit-config.deployment.yaml deleted file mode 100644 index 23f2a21aa..000000000 --- a/.pre-commit-config.deployment.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# pre-commit for deployment related resources -# e.g. shell files, dockerfiles, helm chart etc.. -repos: - - repo: https://github.com/hadolint/hadolint - rev: 'v2.10.0' - hooks: - - id: hadolint - - # Cannot use official repo as it relies on Docker, which cannot be supported by either pre-commit.ci or CircleCI - - repo: https://github.com/Jarmos-san/shellcheck-precommit - rev: 'v0.2.0' - hooks: - - id: shellcheck-system - - - repo: https://github.com/gruntwork-io/pre-commit - rev: 'v0.1.17' - hooks: - - id: helmlint - -ci: - autoupdate_schedule: quarterly diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aeda0f90d..9e7e912a6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,29 +1,20 @@ -# default pre-commit file, checks node.js code and basic file formatting +# pre-commit for deployment related resources +# e.g. shell files, dockerfiles, helm chart etc.. repos: - - repo: https://github.com/pre-commit/mirrors-eslint - rev: 'v8.19.0' # Use the sha / tag you want to point at + - repo: https://github.com/hadolint/hadolint + rev: 'v2.10.0' hooks: - - id: eslint - types: [file] - files: \.[jt]s$|vue$ # *.js, *.ts and vue - exclude: '(\/|^)((generated\/.*)|(\..*\.([jt]sx?|vue)))$' - args: - - '--max-warnings=0' - additional_dependencies: - - eslint@8.11.0 - - eslint-config-prettier@8.5.0 - - eslint-plugin-vue@8.5.0 - - '@babel/eslint-parser@7.18.2' - - '@babel/preset-env@t 7.16.11' - - '@typescript-eslint/eslint-plugin@5.21.0' - - '@typescript-eslint/parser@5.21.0' - - typescript@4.5.4 - - '@rushstack/eslint-patch@1.1.3' - - '@vue/eslint-config-typescript@11.0.0' + - id: hadolint + # Cannot use official repo as it relies on Docker, which cannot be supported by either pre-commit.ci or CircleCI + - repo: https://github.com/Jarmos-san/shellcheck-precommit + rev: 'v0.2.0' + hooks: + - id: shellcheck-system - - repo: https://github.com/pre-commit/mirrors-prettier - rev: 'v2.7.1' # Use the sha / tag you want to point at + - repo: https://github.com/gruntwork-io/pre-commit + rev: 'v0.1.17' hooks: - - id: prettier + - id: helmlint + ci: autoupdate_schedule: quarterly diff --git a/docker-compose-speckle.yml b/docker-compose-speckle.yml index a090ccf17..8cbf2aa8a 100644 --- a/docker-compose-speckle.yml +++ b/docker-compose-speckle.yml @@ -39,6 +39,7 @@ services: S3_SECRET_KEY: 'minioadmin' S3_BUCKET: 'speckle-server' S3_CREATE_BUCKET: 'true' + S3_REGION: '' # optional, defaults to 'us-east-1' FILE_SIZE_LIMIT_MB: 100 preview-service: @@ -72,10 +73,4 @@ services: environment: DEBUG: 'fileimport-service:*' PG_CONNECTION_STRING: 'postgres://speckle:speckle@postgres/speckle' - - S3_ENDPOINT: 'http://minio:9000' - S3_ACCESS_KEY: 'minioadmin' - S3_SECRET_KEY: 'minioadmin' - S3_BUCKET: 'speckle-server' - SPECKLE_SERVER_URL: 'http://speckle-server:3000' diff --git a/package.json b/package.json index 1907f4dd6..179923c85 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,9 @@ "build": "yarn workspaces foreach -ptv run build", "build:public": "yarn workspaces foreach -ptv --no-private run build", "lint": "eslint . --ext .js,.ts,.vue --max-warnings=0", + "helm:readme:generate": "./utils/helm/update-documentation.sh", "prettier:check": "prettier --check .", "prettier:fix": "prettier --write .", - "pre-commit": "pre-commit run --all-files --config .pre-commit-config.yaml && pre-commit run --all-files --config .pre-commit-config.deployment.yaml", "circleci:check": "circleci config validate ./.circleci/config.yml", "dev:docker:up": "docker-compose -f ./docker-compose-deps.yml -f ./docker-compose-dev.yml up -d", "dev:docker:down": "docker-compose -f ./docker-compose-deps.yml -f ./docker-compose-dev.yml down", diff --git a/packages/fileimport-service/package.json b/packages/fileimport-service/package.json index 122f7716d..344638ebd 100644 --- a/packages/fileimport-service/package.json +++ b/packages/fileimport-service/package.json @@ -15,7 +15,7 @@ "node": "^16.0.0" }, "scripts": { - "dev": "cross-env S3_BUCKET=speckle-server POSTGRES_URL=postgres://speckle:speckle@localhost/speckle NODE_ENV=development SPECKLE_SERVER_URL=http://localhost:3000 nodemon ./src/daemon.js", + "dev": "cross-env POSTGRES_URL=postgres://speckle:speckle@localhost/speckle NODE_ENV=development SPECKLE_SERVER_URL=http://localhost:3000 nodemon ./src/daemon.js", "parse:ifc": "node ./ifc/import_file.js /tmp/file_to_import/file 33763848d6 2e4bfb467a main File upload: steelplates.ifc", "lint": "eslint . --ext .js,.ts" }, diff --git a/packages/frontend/nginx/templates/nginx.conf.template b/packages/frontend/nginx/templates/nginx.conf.template index 2f72faac7..16264a8ec 100644 --- a/packages/frontend/nginx/templates/nginx.conf.template +++ b/packages/frontend/nginx/templates/nginx.conf.template @@ -56,11 +56,19 @@ set_real_ip_from 2a06:98c0::/29; real_ip_header CF-Connecting-IP; #real_ip_header X-Forwarded-For; - server { listen 80; client_max_body_size 100m; + # move default write paths to a custom directory + # kubernetes can mount this directory and prevent writes to the root directory + # https://github.com/openresty/docker-openresty/issues/119 + client_body_temp_path /var/run/openresty/nginx-client-body; + proxy_temp_path /var/run/openresty/nginx-proxy; + fastcgi_temp_path /var/run/openresty/nginx-fastcgi; + uwsgi_temp_path /var/run/openresty/nginx-uwsgi; + scgi_temp_path /var/run/openresty/nginx-scgi; + location / { root /usr/share/nginx/html; index app.html; diff --git a/packages/server/modules/blobstorage/objectStorage.js b/packages/server/modules/blobstorage/objectStorage.js index db681a589..7b1be8c73 100644 --- a/packages/server/modules/blobstorage/objectStorage.js +++ b/packages/server/modules/blobstorage/objectStorage.js @@ -27,7 +27,7 @@ const getS3Config = () => { forcePathStyle: true, // s3ForcePathStyle: true, // signatureVersion: 'v4', - region: 'us-east-1' + region: process.env.S3_REGION || 'us-east-1' } } return s3Config diff --git a/packages/server/modules/core/helpers/mainConstants.js b/packages/server/modules/core/helpers/mainConstants.ts similarity index 73% rename from packages/server/modules/core/helpers/mainConstants.js rename to packages/server/modules/core/helpers/mainConstants.ts index b65854675..0611c7b0b 100644 --- a/packages/server/modules/core/helpers/mainConstants.js +++ b/packages/server/modules/core/helpers/mainConstants.ts @@ -1,11 +1,11 @@ -const _ = require('lodash') +import _ from 'lodash' /** * Speckle role constants * - Stream - user roles in the context of a specific stream * - Server - user roles in the context of the entire server */ -const Roles = Object.freeze({ +export const Roles = Object.freeze({ Stream: { Owner: 'stream:owner', Contributor: 'stream:contributor', @@ -18,11 +18,14 @@ const Roles = Object.freeze({ } }) +export type ServerRoles = typeof Roles['Server'][keyof typeof Roles['Server']] +export type StreamRoles = typeof Roles['Stream'][keyof typeof Roles['Stream']] + /** * Speckle scope constants * - Scopes define what kind of access has a user approved for a specific access token */ -const Scopes = Object.freeze({ +export const Scopes = Object.freeze({ Streams: { Read: 'streams:read', Write: 'streams:write' @@ -51,10 +54,4 @@ const Scopes = Object.freeze({ * All scopes * @type {string[]} */ -const AllScopes = _.flatMap(Scopes, (v) => Object.values(v)) - -module.exports = { - Roles, - Scopes, - AllScopes -} +export const AllScopes = _.flatMap(Scopes, (v) => Object.values(v)) diff --git a/packages/server/modules/shared/authz.js b/packages/server/modules/shared/authz.js deleted file mode 100644 index b45703362..000000000 --- a/packages/server/modules/shared/authz.js +++ /dev/null @@ -1,208 +0,0 @@ -const { Roles, Scopes } = require('@/modules/core/helpers/mainConstants') -const { getStream } = require('@/modules/core/services/streams') -const { getRoles } = require('@/modules/shared') -const { - ForbiddenError: SFE, - UnauthorizedError: SUE, - ContextError, - BadRequestError -} = require('@/modules/shared/errors') - -const authFailed = (context, error = null, fatal = false) => ({ - context, - authResult: { authorized: false, error, fatal } -}) -const authSuccess = (context) => ({ - context, - authResult: { authorized: true, error: null } -}) - -const validateRole = - ({ requiredRole, rolesLookup, iddqd, roleGetter }) => - async ({ context, authResult }) => { - const roles = await rolesLookup() - // having the required role doesn't rescue from authResult failure - if (authResult.error) return { context, authResult } - - // role validation has nothing to do with auth... - //this check doesn't belong here, move it out to the auth pipeline - if (!context.auth) - return authFailed(context, new SUE('Cannot validate role without auth')) - - const role = roles.find((r) => r.name === requiredRole) - const myRole = roles.find((r) => r.name === roleGetter(context)) - - if (!role) return authFailed(context, new SFE('Invalid role requirement specified')) - if (!myRole) return authFailed(context, new SFE('Your role is not valid')) - if (myRole.name === iddqd || myRole.weight >= role.weight) - return authSuccess(context) - - return authFailed(context, new SFE('You do not have the required role')) - } - -const validateServerRole = ({ requiredRole }) => - validateRole({ - requiredRole, - rolesLookup: getRoles, - iddqd: Roles.Server.Admin, - roleGetter: (context) => context.role - }) - -const validateStreamRole = ({ requiredRole }) => - validateRole({ - requiredRole, - rolesLookup: getRoles, - iddqd: Roles.Stream.Owner, - roleGetter: (context) => context.stream?.role - }) - -// this could be still useful, if the operation doesnt require a stream context -// const authorizeResolver = refactor the implementation in ../index.js - -const validateScope = - ({ requiredScope }) => - async ({ context, authResult }) => { - // having the required role doesn't rescue from authResult failure - if (authResult.error) return { context, authResult } - if (!context.scopes) - return authFailed(context, new SFE('You do not have the required privileges.')) - if ( - context.scopes.indexOf(requiredScope) === -1 && - context.scopes.indexOf('*') === -1 - ) - return authFailed(context, new SFE('You do not have the required privileges.')) - return authSuccess(context) - } - -// this doesn't do any checks on the scopes, its sole responsibility is to add the -// stream object to the pipeline context -const contextRequiresStream = - (streamGetter) => - // stream getter is an async func over { streamId, userId } returning a stream object - // IoC baby... - async ({ context, authResult, params }) => { - if (!params?.streamId) - return authFailed( - context, - new ContextError("The context doesn't have a streamId") - ) - // because we're assigning to the context, it would raise if it would be null - // its probably?? safer than returning a new context - if (!context) - return authFailed(context, new ContextError('The context is not defined')) - - // cause stream getter could throw, its not a safe function if we want to - // keep the pipeline rolling - try { - const stream = await streamGetter({ - streamId: params.streamId, - userId: context?.userId - }) - if (!stream) - return authFailed( - context, - new BadRequestError('Stream inputs are malformed'), - true - ) - context.stream = stream - return { context, authResult } - } catch (err) { - // this prob needs some more detailing to not leak internal errors - return authFailed(context, new ContextError(err.message)) - } - } - -const allowForRegisteredUsersOnPublicStreamsEvenWithoutRole = async ({ - context, - authResult -}) => - context.auth && context.stream?.isPublic - ? authSuccess(context) - : { context, authResult } - -const allowForAllRegisteredUsersOnPublicStreamsWithPublicComments = async ({ - context, - authResult -}) => - context.auth && context.stream?.isPublic && context.stream?.allowPublicComments - ? authSuccess(context) - : { context, authResult } - -const allowAnonymousUsersOnPublicStreams = async ({ context, authResult }) => { - return context.stream?.isPublic ? authSuccess(context) : { context, authResult } -} - -const authPipelineCreator = (steps) => { - const pipeline = async ({ context, params }) => { - let authResult = { authorized: false, error: null } - for (const step of steps) { - ;({ context, authResult } = await step({ context, authResult, params })) - if (authResult.fatal) break - } - // validate auth result a bit... - if (authResult.authorized && authResult.error) throw new Error('Auth failure') - return { context, authResult } - } - return pipeline -} - -//we could even add an auth middleware creator -// todo move this to a webserver related module, it has no place here -const authMiddlewareCreator = (steps) => { - const pipeline = authPipelineCreator(steps) - - const middleware = async (req, res, next) => { - const { authResult } = await pipeline({ context: req.context, params: req.params }) - if (!authResult.authorized) { - let message = 'Unknown AuthZ error' - let status = 500 - if (authResult.error) { - message = authResult.error.message - if (authResult.error instanceof SUE) status = 401 - if (authResult.error instanceof SFE) status = 403 - } - - return res.status(status).json({ error: message }) - } - next() - } - return middleware -} - -// eslint-disable-next-line no-unused-vars -const exampleMiddleware = authMiddlewareCreator([ - // at some point add the context preparation here too - validateServerRole({ requiredRole: Roles.Server.User }), - validateScope({ requiredScope: Scopes.Streams.Write }), - contextRequiresStream(getStream), - validateStreamRole({ requiredRole: Roles.Stream.Reviewer }), - allowForRegisteredUsersOnPublicStreamsEvenWithoutRole -]) - -module.exports = { - authPipelineCreator, - authSuccess, - authFailed, - validateRole, - validateScope, - validateServerRole, - validateStreamRole, - contextRequiresStream, - ContextError, - authMiddlewareCreator, - allowForRegisteredUsersOnPublicStreamsEvenWithoutRole, - allowForAllRegisteredUsersOnPublicStreamsWithPublicComments, - allowAnonymousUsersOnPublicStreams, - streamWritePermissions: [ - validateServerRole({ requiredRole: Roles.Server.User }), - validateScope({ requiredScope: Scopes.Streams.Write }), - contextRequiresStream(getStream), - validateStreamRole({ requiredRole: Roles.Stream.Contributor }) - ], - streamReadPermissions: [ - validateServerRole({ requiredRole: Roles.Server.User }), - validateScope({ requiredScope: Scopes.Streams.Read }), - contextRequiresStream(getStream), - validateStreamRole({ requiredRole: Roles.Stream.Contributor }) - ] -} diff --git a/packages/server/modules/shared/authz.ts b/packages/server/modules/shared/authz.ts new file mode 100644 index 000000000..aba0aeb09 --- /dev/null +++ b/packages/server/modules/shared/authz.ts @@ -0,0 +1,306 @@ +import Express from 'express' +import { + Scopes, + Roles, + ServerRoles, + StreamRoles +} from '@/modules/core/helpers/mainConstants' +import { getRoles } from '@/modules/shared' +import { getStream } from '@/modules/core/services/streams' + +import { + BaseError, + ForbiddenError, + UnauthorizedError, + ContextError, + BadRequestError +} from '@/modules/shared/errors' +// import { getbAllRoles } from '../core/services/generic' + +interface AuthResult { + authorized: boolean +} + +interface AuthFailedResult extends AuthResult { + authorized: false + error: BaseError | null + fatal?: boolean +} + +interface Stream { + role?: StreamRoles + isPublic: boolean + allowPublicComments: boolean +} + +export interface AuthContext { + auth: boolean + userId?: string + role?: ServerRoles + token?: string + scopes?: string[] + stream?: Stream +} + +interface AuthParams { + streamId?: string +} + +interface AuthData { + context: AuthContext + authResult: AuthResult + params?: AuthParams +} + +interface AuthFailedData extends AuthData { + authResult: AuthFailedResult +} + +export const authFailed = ( + context: AuthContext, + error: BaseError | null, + fatal = false +): AuthFailedData => ({ + context, + authResult: { authorized: false, error, fatal } +}) + +export const authSuccess = (context: AuthContext): AuthData => ({ + context, + authResult: { authorized: true } +}) + +type AvailableRoles = ServerRoles | StreamRoles + +interface RoleData { + weight: number + name: T +} + +type AuthPipelineFunction = ({ + context, + authResult, + params +}: AuthData) => Promise + +const authHasFailed = (authResult: AuthResult): authResult is AuthFailedResult => + 'error' in authResult + +interface RoleValidationInput { + requiredRole: T + rolesLookup: () => Promise[]> + iddqd: T + roleGetter: (context: AuthContext) => T | null +} + +// interface StreamRoleValidationInput { +// requiredRole: StreamRoles +// rolesLookup: () => Promise +// iddqd: StreamRoles +// roleGetter: (AuthContext) => StreamRoles +// } + +export function validateRole({ + requiredRole, + rolesLookup, + iddqd, + roleGetter +}: RoleValidationInput): AuthPipelineFunction { + return async ({ context, authResult }): Promise => { + const roles = await rolesLookup() + //having the required role doesn't rescue from authResult failure + if (authHasFailed(authResult)) return { context, authResult } + + // role validation has nothing to do with auth... + //this check doesn't belong here, move it out to the auth pipeline + if (!context.auth) + return authFailed( + context, + new UnauthorizedError('Cannot validate role without auth') + ) + + const contextRole = roleGetter(context) + if (!contextRole) + return authFailed( + context, + new ForbiddenError('You do not have the required role') + ) + + const role = roles.find((r) => r.name === requiredRole) + const myRole = roles.find((r) => r.name === contextRole) + + if (!role) + return authFailed( + context, + new ForbiddenError('Invalid role requirement specified') + ) + if (!myRole) + return authFailed(context, new ForbiddenError('Your role is not valid')) + if (myRole.name === iddqd || myRole.weight >= role.weight) + return authSuccess(context) + return authFailed(context, new ForbiddenError('You do not have the required role')) + } +} + +export const validateServerRole = ({ requiredRole }: { requiredRole: ServerRoles }) => + validateRole({ + requiredRole, + rolesLookup: getRoles, + iddqd: Roles.Server.Admin, + roleGetter: (context) => context.role || null + }) + +export const validateStreamRole = ({ requiredRole }: { requiredRole: StreamRoles }) => + validateRole({ + requiredRole, + rolesLookup: getRoles, + iddqd: Roles.Stream.Owner, + roleGetter: (context) => context?.stream?.role || null + }) + +export const validateScope = + ({ requiredScope }: { requiredScope: string }): AuthPipelineFunction => + async ({ context, authResult }) => { + // having the required role doesn't rescue from authResult failure + if (authHasFailed(authResult)) return { context, authResult } + if (!context.scopes) + return authFailed( + context, + new ForbiddenError('You do not have the required privileges.') + ) + if ( + context.scopes.indexOf(requiredScope) === -1 && + context.scopes.indexOf('*') === -1 + ) + return authFailed( + context, + new ForbiddenError('You do not have the required privileges.') + ) + return authSuccess(context) + } + +type StreamGetter = (params: { streamId: string; userId?: string }) => Promise + +// this doesn't do any checks on the scopes, its sole responsibility is to add the +// stream object to the pipeline context +export const contextRequiresStream = + (streamGetter: StreamGetter): AuthPipelineFunction => + // stream getter is an async func over { streamId, userId } returning a stream object + // IoC baby... + async ({ context, authResult, params }) => { + if (!params?.streamId) + return authFailed( + context, + new ContextError("The context doesn't have a streamId") + ) + // because we're assigning to the context, it would raise if it would be null + // its probably?? safer than returning a new context + if (!context) + return authFailed(context, new ContextError('The context is not defined')) + + // cause stream getter could throw, its not a safe function if we want to + // keep the pipeline rolling + try { + const stream = await streamGetter({ + streamId: params.streamId, + userId: context?.userId + }) + if (!stream) + return authFailed( + context, + new BadRequestError('Stream inputs are malformed'), + true + ) + context.stream = stream + return { context, authResult } + } catch (err) { + // this prob needs some more detailing to not leak internal errors + const error = err as Error + return authFailed(context, new ContextError(error.message)) + } + } + +export const allowForRegisteredUsersOnPublicStreamsEvenWithoutRole: AuthPipelineFunction = + async ({ context, authResult }) => + context.auth && context.stream?.isPublic + ? authSuccess(context) + : { context, authResult } + +export const allowForAllRegisteredUsersOnPublicStreamsWithPublicComments: AuthPipelineFunction = + async ({ context, authResult }) => + context.auth && context.stream?.isPublic && context.stream?.allowPublicComments + ? authSuccess(context) + : { context, authResult } + +export const allowAnonymousUsersOnPublicStreams: AuthPipelineFunction = async ({ + context, + authResult +}) => (context.stream?.isPublic ? authSuccess(context) : { context, authResult }) + +export const authPipelineCreator = ( + steps: AuthPipelineFunction[] +): AuthPipelineFunction => { + const pipeline: AuthPipelineFunction = async ({ + context, + params, + authResult = { authorized: false } + }) => { + for (const step of steps) { + ;({ context, authResult } = await step({ context, authResult, params })) + if (authHasFailed(authResult) && authResult?.fatal) break + } + // validate auth result a bit... + if (authResult.authorized && authHasFailed(authResult)) + throw new Error('Auth failure') + return { context, authResult } + } + return pipeline +} + +interface RequestWithContext extends Express.Request { + context: AuthContext +} + +//we could even add an auth middleware creator +// todo move this to a webserver related module, it has no place here +export const authMiddlewareCreator = (steps: AuthPipelineFunction[]) => { + const pipeline = authPipelineCreator(steps) + + const middleware = async ( + req: RequestWithContext, + res: Express.Response, + next: Express.NextFunction + ) => { + const { authResult } = await pipeline({ + context: req.context as AuthContext, + params: req.params as AuthParams, + authResult: { authorized: false } + }) + if (!authResult.authorized) { + let message = 'Unknown AuthZ error' + let status = 500 + if (authHasFailed(authResult)) { + message = authResult.error?.message || message + if (authResult.error instanceof UnauthorizedError) status = 401 + if (authResult.error instanceof ForbiddenError) status = 403 + } + + return res.status(status).json({ error: message }) + } + next() + } + return middleware +} + +export const streamWritePermissions = [ + validateServerRole({ requiredRole: Roles.Server.User }), + validateScope({ requiredScope: Scopes.Streams.Write }), + contextRequiresStream(getStream as StreamGetter), + validateStreamRole({ requiredRole: Roles.Stream.Contributor }) +] +export const streamReadPermissions = [ + validateServerRole({ requiredRole: Roles.Server.User }), + validateScope({ requiredScope: Scopes.Streams.Read }), + contextRequiresStream(getStream as StreamGetter), + validateStreamRole({ requiredRole: Roles.Stream.Contributor }) +] diff --git a/packages/server/modules/shared/errors/base.js b/packages/server/modules/shared/errors/base.ts similarity index 81% rename from packages/server/modules/shared/errors/base.js rename to packages/server/modules/shared/errors/base.ts index 574ea7396..ea5730f1f 100644 --- a/packages/server/modules/shared/errors/base.js +++ b/packages/server/modules/shared/errors/base.ts @@ -1,4 +1,4 @@ -const VError = require('verror') +import { VError, Options } from 'verror' /** * Base application error (don't use directly, treat it as abstract). Built on top of `verror` so that you can @@ -6,7 +6,7 @@ const VError = require('verror') * * This allows for much nicer error handling & monitoring */ -class BaseError extends VError { +export class BaseError extends VError { /** * Error code (override in child class) */ @@ -17,11 +17,10 @@ class BaseError extends VError { */ static defaultMessage = 'Unexpected error occurred!' - /** - * @param {string | null} message - * @param {import('verror').Options | Error} options - */ - constructor(message, options) { + constructor( + message: string | null | undefined, + options: Options | Error | undefined = undefined + ) { // Resolve options correctly if (options) { const cause = options instanceof Error ? options : options.cause @@ -53,8 +52,6 @@ class BaseError extends VError { * Get collected info of this object and previous errors */ info() { - return BaseError.info(this) + return BaseError.info(this as unknown as Error) } } - -module.exports = { BaseError } diff --git a/packages/server/modules/shared/errors/index.js b/packages/server/modules/shared/errors/index.ts similarity index 66% rename from packages/server/modules/shared/errors/index.js rename to packages/server/modules/shared/errors/index.ts index 957e278f6..f87a62662 100644 --- a/packages/server/modules/shared/errors/index.js +++ b/packages/server/modules/shared/errors/index.ts @@ -1,6 +1,6 @@ -const { BaseError } = require('./base') +import { BaseError } from '@/modules/shared/errors/base' -class ForbiddenError extends BaseError { +export class ForbiddenError extends BaseError { static code = 'FORBIDDEN_ERROR' static defaultMessage = 'Access to the resource is forbidden' } @@ -9,7 +9,7 @@ class ForbiddenError extends BaseError { * Use this in logic branches that should never execute, and if they do - it means * there's most definitely a bug in the code */ -class LogicError extends BaseError { +export class LogicError extends BaseError { static code = 'LOGIC_ERROR' static defaultMessage = 'An unexpected issue occurred' } @@ -17,51 +17,42 @@ class LogicError extends BaseError { /** * Use this to throw when user tries to access data that he shouldn't have access to */ -class UnauthorizedError extends BaseError { +export class UnauthorizedError extends BaseError { static code = 'UNAUTHORIZED_ACCESS_ERROR' static defaultMessage = 'Attempted unauthorized access to data' } -class NotFoundError extends BaseError { +export class NotFoundError extends BaseError { static code = 'NOT_FOUND_ERROR' static defaultMessage = "These aren't the droids you're looking for." } -class BadRequestError extends BaseError { +export class BadRequestError extends BaseError { static code = 'BAD_REQUEST_ERROR' static defaultMessage = 'The request contains invalid data' } -class ResourceMismatch extends BaseError { +export class ResourceMismatch extends BaseError { static code = 'BAD_REQUEST_ERROR' static defaultMessage = 'The target resources mismatch' } /** * Use this to validate args */ -class InvalidArgumentError extends BaseError { +export class InvalidArgumentError extends BaseError { static code = 'INVALID_ARGUMENT_ERROR' static defaultMessage = 'Invalid arguments received' } -class RichTextParseError extends BaseError { + +export class RichTextParseError extends BaseError { static code = 'RICH_TEXT_PARSE_ERROR' static defaultMessage = 'An error occurred while trying to parse the rich text document' } -class ContextError extends BaseError { +export class ContextError extends BaseError { static code = 'CONTEXT_ERROR' static defaultMessage = 'The context is missing from the request' } -module.exports = { - BadRequestError, - ForbiddenError, - UnauthorizedError, - NotFoundError, - ResourceMismatch, - InvalidArgumentError, - RichTextParseError, - ContextError, - LogicError -} +export { BaseError } diff --git a/packages/server/modules/shared/index.js b/packages/server/modules/shared/index.js index a0e949469..88cf6c808 100644 --- a/packages/server/modules/shared/index.js +++ b/packages/server/modules/shared/index.js @@ -19,21 +19,12 @@ const pubsub = new RedisPubSub({ }) /** - * @typedef {Object} AuthContextPart - * @property {boolean} auth Whether or not user is logged in - * @property {string | undefined} userId User ID, if user is logged in - * @property {string | undefined} role User role, if logged in - * @property {string | undefined} token User token, if logged in - * @property {string[] | undefined} scopes Token scopes, if logged in - */ - -/** - * @typedef {AuthContextPart & {loaders: import('@/modules/core/loaders').RequestDataLoaders}} GraphQLContext + * @typedef {import('@/modules/shared/authz').AuthContext & {loaders: import('@/modules/core/loaders').RequestDataLoaders}} GraphQLContext */ /** * Add data loaders to auth ctx - * @param {AuthContextPart} ctx + * @param {import('@/modules/shared/authz').AuthContext} ctx * @returns {GraphQLContext} */ async function addLoadersToCtx(ctx) { @@ -56,7 +47,7 @@ async function buildContext({ req, connection }) { /** * Not just Graphql server context helper: sets req.context to have an auth prop (true/false), userId and server role. - * @returns {AuthContextPart} + * @returns {import('@/modules/shared/authz').AuthContext} */ async function contextApiTokenHelper({ req, connection }) { let token = null diff --git a/packages/server/modules/shared/test/authz.spec.js b/packages/server/modules/shared/test/authz.spec.js index 0e58069e6..3f755f76a 100644 --- a/packages/server/modules/shared/test/authz.spec.js +++ b/packages/server/modules/shared/test/authz.spec.js @@ -6,7 +6,6 @@ const { validateRole, validateScope, contextRequiresStream, - ContextError, allowForAllRegisteredUsersOnPublicStreamsWithPublicComments, allowForRegisteredUsersOnPublicStreamsEvenWithoutRole } = require('@/modules/shared/authz') @@ -14,7 +13,8 @@ const { ForbiddenError: SFE, UnauthorizedError: SUE, BadRequestError, - UnauthorizedError + UnauthorizedError, + ContextError } = require('@/modules/shared/errors') describe('AuthZ @shared', () => { @@ -45,7 +45,11 @@ describe('AuthZ @shared', () => { }) it('Pipeline throws Error if authorized but has error', async () => { const borkedStep = async () => ({ - authResult: { authorized: true, error: new UnauthorizedError('Weird stuff') } + authResult: { + authorized: true, + error: new UnauthorizedError('Weird stuff'), + fatal: false + } }) const pipeline = authPipelineCreator([borkedStep]) try { diff --git a/packages/server/scripts/streamObjects.js b/packages/server/scripts/streamObjects.js new file mode 100644 index 000000000..0272b8f4e --- /dev/null +++ b/packages/server/scripts/streamObjects.js @@ -0,0 +1,73 @@ +require('../bootstrap') +const { getUserByEmail } = require('@/modules/core/services/users') +const { createPersonalAccessToken } = require('@/modules/core/services/tokens') +const { createStream } = require('@/modules/core/services/streams') + +const { createManyObjects } = require('@/test/helpers') +const { fetch } = require('undici') +const { init } = require(`@/app`) +const request = require('supertest') +const { exit } = require('yargs') + +const main = async () => { + const testStream = { + name: 'Test Stream 01', + description: 'wonderful test stream' + } + + // const userA = { + // name: 'd1', + // email: 'd.1@speckle.systems', + // password: 'wowwow8charsplease' + // } + // userA.id = await createUser(userA) + + const userA = await getUserByEmail({ + email: 'd.1@speckle.systems' + }) + userA.token = `Bearer ${await createPersonalAccessToken( + userA.id, + 'test token user A', + [ + 'streams:read', + 'streams:write', + 'users:read', + 'users:email', + 'tokens:write', + 'tokens:read', + 'profile:read', + 'profile:email' + ] + )}` + + testStream.id = await createStream({ ...testStream, ownerId: userA.id }) + + const { app } = await init() + + const numObjs = 5000 + const objBatch = createManyObjects(numObjs) + + const uploadRes = await request(app) + .post(`/objects/${testStream.id}`) + .set('Authorization', userA.token) + .set('Content-type', 'multipart/form-data') + .attach('batch1', Buffer.from(JSON.stringify(objBatch), 'utf8')) + + console.log(uploadRes.status) + const objectIds = objBatch.map((obj) => obj.id) + + const res = await fetch(`http://localhost:3000/api/getobjects/${testStream.id}`, { + method: 'POST', + headers: { + Authorization: userA.token, + 'Content-Type': 'application/json', + Accept: 'text/plain' + }, + body: JSON.stringify({ objects: JSON.stringify(objectIds) }) + }) + const data = await res.body.getReader().read() + console.log(data) + exit(0) +} + +main().then(console.log('created')).catch(console.log('failed')) diff --git a/utils/1click_image_scripts/template-docker-compose.yml b/utils/1click_image_scripts/template-docker-compose.yml index c6737fc36..26dc327b9 100644 --- a/utils/1click_image_scripts/template-docker-compose.yml +++ b/utils/1click_image_scripts/template-docker-compose.yml @@ -79,6 +79,7 @@ services: S3_SECRET_KEY: 'minioadmin' S3_BUCKET: 'speckle-server' S3_CREATE_BUCKET: 'true' + S3_REGION: '' # optional, defaults to 'us-east-1' FILE_SIZE_LIMIT_MB: 100 @@ -110,11 +111,5 @@ services: environment: DEBUG: 'fileimport-service:*' PG_CONNECTION_STRING: 'postgres://speckle:speckle@postgres/speckle' - WAIT_HOSTS: 'postgres:5432, minio:9000' - - S3_ENDPOINT: 'http://minio:9000' - S3_ACCESS_KEY: 'minioadmin' - S3_SECRET_KEY: 'minioadmin' - S3_BUCKET: 'speckle-server' - + WAIT_HOSTS: 'postgres:5432' SPECKLE_SERVER_URL: 'http://speckle-server:3000' diff --git a/utils/helm/.helm-readme-configuration.json b/utils/helm/.helm-readme-configuration.json new file mode 100644 index 000000000..778170964 --- /dev/null +++ b/utils/helm/.helm-readme-configuration.json @@ -0,0 +1,22 @@ +{ + "comments": { + "format": "##" + }, + "tags": { + "param": "@param", + "section": "@section", + "descriptionStart": "@descriptionStart", + "descriptionEnd": "@descriptionEnd", + "skip": "@skip", + "extra": "@extra" + }, + "modifiers": { + "array": "array", + "object": "object", + "string": "string", + "nullable": "nullable" + }, + "regexp": { + "paramsSectionTitle": "Parameters" + } +} diff --git a/utils/helm/Makefile b/utils/helm/Makefile index 8b54257d3..0e6d9a564 100644 --- a/utils/helm/Makefile +++ b/utils/helm/Makefile @@ -7,6 +7,7 @@ build: cd ../.. && docker build -t speckle/speckle-webhook-service:local -f packages/webhook-service/Dockerfile . cd ../.. && docker build -t speckle/speckle-fileimport-service:local -f packages/fileimport-service/Dockerfile . cd ../.. && docker build -t speckle/speckle-monitor-deployment:local -f utils/monitor-deployment/Dockerfile . + cd ../.. && docker build -t speckle/speckle-test-deployment:local -f utils/test-deployment/Dockerfile . echo "Making locally built images available inside minikube cluster. This takes a bit to copy, unfortunately..." @@ -16,6 +17,7 @@ build: minikube image load speckle/speckle-webhook-service:local minikube image load speckle/speckle-fileimport-service:local minikube image load speckle/speckle-monitor-deployment:local + minikube image load speckle/speckle-test-deployment:local install: diff --git a/utils/helm/speckle-server/templates/_helpers.tpl b/utils/helm/speckle-server/templates/_helpers.tpl index b06b670e9..5272acc3c 100644 --- a/utils/helm/speckle-server/templates/_helpers.tpl +++ b/utils/helm/speckle-server/templates/_helpers.tpl @@ -93,3 +93,143 @@ Part-of label {{- define "speckle.labels.part-of" -}} app.kubernetes.io/part-of: {{ include "speckle.name" . }} {{- end }} + +{{/* +Creates a network policy egress definition for connecting to Redis + +Expects the global context "$" to be passed as the parameter +*/}} +{{- define "speckle.networkpolicy.egress.redis" -}} +{{- $port := (default "6379" .Values.redis.networkPolicy.port ) -}} +{{- if .Values.redis.networkPolicy.inCluster.enabled -}} +{{ include "speckle.networkpolicy.egress.internal" (dict "podSelector" .Values.redis.networkPolicy.inCluster.podSelector "namespaceSelector" .Values.redis.networkPolicy.inCluster.namespaceSelector "port" $port) }} +{{- else if .Values.redis.networkPolicy.externalToCluster.enabled -}} +{{ include "speckle.networkpolicy.egress.external" (dict "ip" .Values.redis.networkPolicy.externalToCluster.ipv4 "port" $port) }} +{{- end -}} +{{- end }} + +{{/* +Creates a network policy egress definition for connecting to Postgres +*/}} +{{- define "speckle.networkpolicy.egress.postgres" -}} +{{- $port := (default "5432" .Values.db.networkPolicy.port ) -}} +{{- if .Values.db.networkPolicy.inCluster.enabled -}} +{{ include "speckle.networkpolicy.egress.internal" (dict "podSelector" .Values.db.networkPolicy.inCluster.podSelector "namespaceSelector" .Values.db.networkPolicy.inCluster.namespaceSelector "port" $port) }} +{{- else if .Values.db.networkPolicy.externalToCluster.enabled -}} +{{ include "speckle.networkpolicy.egress.external" (dict "ip" .Values.db.networkPolicy.externalToCluster.ipv4 "port" $port) }} +{{- end -}} +{{- end }} + +{{/* +Creates a network policy egress definition for connecting to Postgres +*/}} +{{- define "speckle.networkpolicy.egress.blob_storage" -}} +{{- $port := (default "443" .Values.s3.networkPolicy.port ) -}} +{{- if .Values.s3.networkPolicy.inCluster.enabled -}} +{{ include "speckle.networkpolicy.egress.internal" (dict "podSelector" .Values.s3.networkPolicy.inCluster.podSelector "namespaceSelector" .Values.s3.networkPolicy.inCluster.namespaceSelector "port" $port) }} +{{- else if .Values.s3.networkPolicy.externalToCluster.enabled -}} + {{- $host := (urlParse .Values.s3.endpoint).host -}} + {{- if (contains ":" $host) -}} + {{- $host = first (mustRegexSplit ":" $host) -}} + {{- end -}} + {{- $ip := "" -}} + {{- if eq (include "speckle.isIPv4" $host) "true" -}} + {{- $ip = $host -}} + {{- end -}} +{{ include "speckle.networkpolicy.egress.external" (dict "ip" $ip "port" $port) }} +{{- end -}} +{{- end }} + +{{/* +Creates a network policy egress definition for connecting to an external url:port or ip:port + +Usage: +{{ include "speckle.networkpolicy.egress.external" (dict "ip" "" "port" "6379") }} + +Params: + - ip - String - Optional - If the IP is not known, then egress is allowed to 0.0.0.0/0. + - port - String - Required + +Limitations: + - IP is limited to IPv4 due to Kubernetes use of IPv4 CIDR + - Kubernetes network policies do not support FQDN, hence if IP is not known egress is allowed to 0.0.0.0/0 + +*/}} +{{- define "speckle.networkpolicy.egress.external" -}} +{{- if not .port -}} + {{- printf "\nNETWORKPOLICY ERROR: The port was not provided \"%s\"\n" .port | fail -}} +{{- end -}} +- to: + - ipBlock: + {{- if .ip }} + cidr: {{ printf "%s/32" .ip }} + {{- else }} + # Kubernetes network policy does not support fqdn, so we have to allow egress anywhere + cidr: 0.0.0.0/0 + # except to kubernetes pods or services + except: + - 10.0.0.0/8 + {{- end }} + ports: + - port: {{ printf "%s" .port }} +{{- end }} + +{{/* +Creates a network policy egress definition for connecting to a pod within the cluster + +Usage: +{{ include "speckle.networkpolicy.egress.internal" (dict "podSelectorLabels" {matchLabels.name=redis} "namespaceSelector" {matchLabels.name=redis} "port" "6379") }} + +Params: + - podSelector - Object - Required + - namespaceSelector - Object - Required + - port - String - Required + +*/}} +{{- define "speckle.networkpolicy.egress.internal" -}} +{{- if not .podSelector -}} + {{- printf "\nNETWORKPOLICY ERROR: The pod selector was not provided\n" | fail -}} +{{- end -}} +{{- if not .namespaceSelector -}} + {{- printf "\nNETWORKPOLICY ERROR: The namespace selector was not provided\n" | fail -}} +{{- end -}} +{{- if not .port -}} + {{- printf "\nNETWORKPOLICY ERROR: The port was not provided \"%s\"\n" .port | fail -}} +{{- end -}} +- to: + - namespaceSelector: +{{ .namespaceSelector | toYaml | indent 8 }} + podSelector: +{{ .podSelector | toYaml | indent 8 }} + ports: + - port: {{ printf "%s" .port }} +{{- end }} + +{{/* +Tries to determine if a given string is a valid IP address +Usage: +{{ include "speckle.isIPv4" "123.45.67.89" }} + +Params: + - ip - String - Required - The string which we will try to determine is a valid IP address +*/}} +{{- define "speckle.isIPv4" -}} +{{- if regexMatch "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" . -}} +{{- printf "true" -}} +{{- else -}} +{{- printf "false" -}} +{{- end -}} +{{- end -}} + +{{/* +Renders a value that contains template. +Usage: +{{ include "speckle.renderTpl" ( dict "value" .Values.path.to.value "context" $) }} +*/}} +{{- define "speckle.renderTpl" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/fileimport_service/deployment.yml b/utils/helm/speckle-server/templates/fileimport_service/deployment.yml index 3aada8c5f..aac7b446a 100644 --- a/utils/helm/speckle-server/templates/fileimport_service/deployment.yml +++ b/utils/helm/speckle-server/templates/fileimport_service/deployment.yml @@ -44,8 +44,20 @@ spec: cpu: {{ .Values.fileimport_service.limits.cpu }} memory: {{ .Values.fileimport_service.limits.memory }} - {{- if .Values.db.useCertificate }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 20000 + volumeMounts: + - mountPath: /tmp + name: tmp + {{- if .Values.db.useCertificate }} - name: postgres-certificate mountPath: /postgres-certificate {{- end }} @@ -53,7 +65,7 @@ spec: env: - name: SPECKLE_SERVER_URL value: "http://speckle-server:3000" - + - name: PG_CONNECTION_STRING valueFrom: secretKeyRef: @@ -68,31 +80,42 @@ spec: value: "/postgres-certificate/ca-certificate.crt" {{- end }} - - - name: S3_ENDPOINT - value: {{ .Values.s3.endpoint }} - - name: S3_ACCESS_KEY - value: {{ .Values.s3.access_key }} - - name: S3_BUCKET - value: {{ .Values.s3.bucket }} - - name: S3_SECRET_KEY - valueFrom: - secretKeyRef: - name: {{ .Values.secretName }} - key: s3_secret_key - - name: FILE_IMPORT_TIME_LIMIT_MIN value: {{ .Values.fileimport_service.time_limit_min | quote }} - priorityClassName: low-priority + {{- if .Values.fileimport_service.affinity }} + affinity: {{- include "speckle.renderTpl" (dict "value" .Values.fileimport_service.affinity "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.fileimport_service.nodeSelector }} + nodeSelector: {{- include "speckle.renderTpl" (dict "value" .Values.fileimport_service.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.fileimport_service.tolerations }} + tolerations: {{- include "speckle.renderTpl" (dict "value" .Values.fileimport_service.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.fileimport_service.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "speckle.renderTpl" (dict "value" .Values.fileimport_service.topologySpreadConstraints "context" $) | nindent 8 }} + {{- end }} - {{- if .Values.db.useCertificate }} + securityContext: + runAsNonRoot: true + runAsUser: 20000 + fsGroup: 25000 + fsGroupChangePolicy: OnRootMismatch + runAsGroup: 30000 + seccompProfile: + type: RuntimeDefault + priorityClassName: low-priority + {{- if .Values.fileimport_service.serviceAccount.create }} + serviceAccountName: {{ include "fileimport_service.name" $ }} + {{- end }} + # Should be > File import timeout to allow finishing up imports + terminationGracePeriodSeconds: 610 volumes: + - name: tmp + emptyDir: {} + {{- if .Values.db.useCertificate }} - name: postgres-certificate configMap: name: postgres-certificate {{- end }} - - # Should be > File import timeout to allow finishing up imports - terminationGracePeriodSeconds: 610 {{- end }} diff --git a/utils/helm/speckle-server/templates/fileimport_service/networkpolicy.yml b/utils/helm/speckle-server/templates/fileimport_service/networkpolicy.yml new file mode 100644 index 000000000..e7a92305b --- /dev/null +++ b/utils/helm/speckle-server/templates/fileimport_service/networkpolicy.yml @@ -0,0 +1,45 @@ +{{- if .Values.fileimport_service.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "fileimport_service.name" $ }} + namespace: {{ .Values.namespace }} + labels: +{{ include "fileimport_service.labels" . | indent 4 }} +spec: + podSelector: + matchLabels: +{{ include "fileimport_service.selectorLabels" . | indent 6 }} + policyTypes: + - Egress + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ default .Values.namespace .Values.prometheusMonitoring.namespace }} + podSelector: + matchLabels: + prometheus: {{ default "kube-prometheus-stack" .Values.prometheusMonitoring.release }}-prometheus + ports: + - port: metrics + egress: + # allow access to DNS + - to: + - namespaceSelector: {} + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - port: 53 + protocol: UDP + # allow egress to speckle-server + - to: + - podSelector: + matchLabels: +{{ include "server.selectorLabels" $ | indent 16 }} + ports: + - port: 3000 + # postgres +{{ include "speckle.networkpolicy.egress.postgres" $ | indent 4 }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/fileimport_service/serviceaccount.yml b/utils/helm/speckle-server/templates/fileimport_service/serviceaccount.yml new file mode 100644 index 000000000..cb2b01e34 --- /dev/null +++ b/utils/helm/speckle-server/templates/fileimport_service/serviceaccount.yml @@ -0,0 +1,14 @@ +{{- if .Values.fileimport_service.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "fileimport_service.name" $ }} + namespace: {{ .Values.namespace | quote }} + labels: +{{ include "fileimport_service.labels" $ | indent 4 }} + annotations: + "kubernetes.io/enforce-mountable-secrets": "true" +automountServiceAccountToken: false +secrets: + - name: {{ .Values.secretName }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/frontend/deployment.yml b/utils/helm/speckle-server/templates/frontend/deployment.yml index 517c8bfb5..3c5d9096b 100644 --- a/utils/helm/speckle-server/templates/frontend/deployment.yml +++ b/utils/helm/speckle-server/templates/frontend/deployment.yml @@ -46,9 +46,24 @@ spec: port: www initialDelaySeconds: 5 periodSeconds: 5 - + env: - name: FILE_SIZE_LIMIT_MB value: {{ .Values.file_size_limit_mb | quote }} priorityClassName: high-priority + {{- if .Values.frontend.affinity }} + affinity: {{- include "speckle.renderTpl" (dict "value" .Values.frontend.affinity "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.frontend.nodeSelector }} + nodeSelector: {{- include "speckle.renderTpl" (dict "value" .Values.frontend.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.frontend.tolerations }} + tolerations: {{- include "speckle.renderTpl" (dict "value" .Values.frontend.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.frontend.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "speckle.renderTpl" (dict "value" .Values.frontend.topologySpreadConstraints "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.frontend.serviceAccount.create }} + serviceAccountName: {{ include "frontend.name" $ }} + {{- end }} diff --git a/utils/helm/speckle-server/templates/frontend/networkpolicy.yml b/utils/helm/speckle-server/templates/frontend/networkpolicy.yml new file mode 100644 index 000000000..57ba8114a --- /dev/null +++ b/utils/helm/speckle-server/templates/frontend/networkpolicy.yml @@ -0,0 +1,28 @@ +{{- if .Values.frontend.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "frontend.name" $ }} + namespace: {{ .Values.namespace }} + labels: +{{ include "frontend.labels" . | indent 4 }} +spec: + podSelector: + matchLabels: +{{ include "frontend.selectorLabels" . | indent 6 }} + policyTypes: + - Ingress + - Egress + ingress: + # allow ingress from the loadbalancer + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .Values.ingress.namespace }} + - podSelector: + matchLabels: + app.kubernetes.io/name: {{ .Values.ingress.controllerName }} + ports: + - port: www + egress: [] # block all egress +{{- end -}} diff --git a/utils/helm/speckle-server/templates/frontend/serviceaccount.yml b/utils/helm/speckle-server/templates/frontend/serviceaccount.yml new file mode 100644 index 000000000..8a2877409 --- /dev/null +++ b/utils/helm/speckle-server/templates/frontend/serviceaccount.yml @@ -0,0 +1,13 @@ +{{- if .Values.frontend.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "frontend.name" $ }} + namespace: {{ .Values.namespace | quote }} + labels: +{{ include "frontend.labels" $ | indent 4 }} + annotations: + "kubernetes.io/enforce-mountable-secrets": "true" +automountServiceAccountToken: false +secrets: [] # no access to any secret +{{- end -}} diff --git a/utils/helm/speckle-server/templates/monitoring/deployment.yml b/utils/helm/speckle-server/templates/monitoring/deployment.yml index fdcb37e98..dc4c7acf3 100644 --- a/utils/helm/speckle-server/templates/monitoring/deployment.yml +++ b/utils/helm/speckle-server/templates/monitoring/deployment.yml @@ -34,6 +34,15 @@ spec: cpu: {{ .Values.monitoring.limits.cpu }} memory: {{ .Values.monitoring.limits.memory }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 20000 {{- if .Values.db.useCertificate }} volumeMounts: @@ -54,6 +63,18 @@ spec: {{- end }} priorityClassName: low-priority + {{- if .Values.monitoring.serviceAccount.create }} + serviceAccountName: {{ include "monitoring.name" $ }} + {{- end }} + terminationGracePeriodSeconds: 10 + securityContext: + runAsNonRoot: true + runAsUser: 20000 + fsGroup: 25000 + fsGroupChangePolicy: OnRootMismatch + runAsGroup: 30000 + seccompProfile: + type: RuntimeDefault {{- if .Values.db.useCertificate }} volumes: @@ -62,4 +83,15 @@ spec: name: postgres-certificate {{- end }} - terminationGracePeriodSeconds: 10 + {{- if .Values.monitoring.affinity }} + affinity: {{- include "speckle.renderTpl" (dict "value" .Values.monitoring.affinity "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.monitoring.nodeSelector }} + nodeSelector: {{- include "speckle.renderTpl" (dict "value" .Values.monitoring.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.monitoring.tolerations }} + tolerations: {{- include "speckle.renderTpl" (dict "value" .Values.monitoring.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.monitoring.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "speckle.renderTpl" (dict "value" .Values.monitoring.topologySpreadConstraints "context" $) | nindent 8 }} + {{- end }} diff --git a/utils/helm/speckle-server/templates/monitoring/networkpolicy.yml b/utils/helm/speckle-server/templates/monitoring/networkpolicy.yml new file mode 100644 index 000000000..307bd60fd --- /dev/null +++ b/utils/helm/speckle-server/templates/monitoring/networkpolicy.yml @@ -0,0 +1,38 @@ +{{- if .Values.monitoring.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "monitoring.name" $ }} + namespace: {{ .Values.namespace }} + labels: +{{ include "monitoring.labels" . | indent 4 }} +spec: + podSelector: + matchLabels: +{{ include "monitoring.selectorLabels" . | indent 6 }} + policyTypes: + - Egress + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ default .Values.namespace .Values.prometheusMonitoring.namespace }} + podSelector: + matchLabels: + prometheus: {{ default "kube-prometheus-stack" .Values.prometheusMonitoring.release }}-prometheus + ports: + - port: metrics + egress: + # allow access to DNS + - to: + - namespaceSelector: {} + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - port: 53 + protocol: UDP + # postgres +{{ include "speckle.networkpolicy.egress.postgres" $ | indent 4 }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/monitoring/serviceaccount.yml b/utils/helm/speckle-server/templates/monitoring/serviceaccount.yml new file mode 100644 index 000000000..687d2f61c --- /dev/null +++ b/utils/helm/speckle-server/templates/monitoring/serviceaccount.yml @@ -0,0 +1,14 @@ +{{- if .Values.monitoring.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "monitoring.name" $ }} + namespace: {{ .Values.namespace | quote }} + labels: +{{ include "monitoring.labels" $ | indent 4 }} + annotations: + "kubernetes.io/enforce-mountable-secrets": "true" +automountServiceAccountToken: false +secrets: + - name: {{ .Values.secretName }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/preview_service/deployment.yml b/utils/helm/speckle-server/templates/preview_service/deployment.yml index cd8799b43..ad98d3eed 100644 --- a/utils/helm/speckle-server/templates/preview_service/deployment.yml +++ b/utils/helm/speckle-server/templates/preview_service/deployment.yml @@ -43,8 +43,20 @@ spec: cpu: {{ .Values.preview_service.limits.cpu }} memory: {{ .Values.preview_service.limits.memory }} - {{- if .Values.db.useCertificate }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 20000 + volumeMounts: + - mountPath: /tmp + name: tmp + {{- if .Values.db.useCertificate }} - name: postgres-certificate mountPath: /postgres-certificate {{- end }} @@ -64,13 +76,39 @@ spec: value: "/postgres-certificate/ca-certificate.crt" {{- end }} + {{- if .Values.preview_service.affinity }} + affinity: {{- include "speckle.renderTpl" (dict "value" .Values.preview_service.affinity "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.preview_service.nodeSelector }} + nodeSelector: {{- include "speckle.renderTpl" (dict "value" .Values.preview_service.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.preview_service.tolerations }} + tolerations: {{- include "speckle.renderTpl" (dict "value" .Values.preview_service.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.preview_service.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "speckle.renderTpl" (dict "value" .Values.preview_service.topologySpreadConstraints "context" $) | nindent 8 }} + {{- end }} priorityClassName: low-priority + {{- if .Values.preview_service.serviceAccount.create }} + serviceAccountName: {{ include "preview_service.name" $ }} + {{- end }} + + securityContext: + runAsNonRoot: true + runAsUser: 20000 + fsGroup: 25000 + fsGroupChangePolicy: OnRootMismatch + runAsGroup: 30000 + seccompProfile: + type: RuntimeDefault # Should be > preview generation time ( 1 hour for good measure ) terminationGracePeriodSeconds: 3600 - {{- if .Values.db.useCertificate }} volumes: + - name: tmp + emptyDir: {} + {{- if .Values.db.useCertificate }} - name: postgres-certificate configMap: name: postgres-certificate diff --git a/utils/helm/speckle-server/templates/preview_service/networkpolicy.yml b/utils/helm/speckle-server/templates/preview_service/networkpolicy.yml new file mode 100644 index 000000000..76ad67b9e --- /dev/null +++ b/utils/helm/speckle-server/templates/preview_service/networkpolicy.yml @@ -0,0 +1,38 @@ +{{- if .Values.preview_service.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "preview_service.name" $ }} + namespace: {{ .Values.namespace }} + labels: +{{ include "preview_service.labels" . | indent 4 }} +spec: + podSelector: + matchLabels: +{{ include "preview_service.selectorLabels" . | indent 6 }} + policyTypes: + - Egress + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ default .Values.namespace .Values.prometheusMonitoring.namespace }} + podSelector: + matchLabels: + prometheus: {{ default "kube-prometheus-stack" .Values.prometheusMonitoring.release }}-prometheus + ports: + - port: metrics + egress: + # allow access to DNS + - to: + - namespaceSelector: {} + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - port: 53 + protocol: UDP + # postgres +{{ include "speckle.networkpolicy.egress.postgres" $ | indent 4 }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/preview_service/serviceaccount.yml b/utils/helm/speckle-server/templates/preview_service/serviceaccount.yml new file mode 100644 index 000000000..bc6a25c65 --- /dev/null +++ b/utils/helm/speckle-server/templates/preview_service/serviceaccount.yml @@ -0,0 +1,14 @@ +{{- if .Values.preview_service.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "preview_service.name" $ }} + namespace: {{ .Values.namespace | quote }} + labels: +{{ include "preview_service.labels" $ | indent 4 }} + annotations: + "kubernetes.io/enforce-mountable-secrets": "true" +automountServiceAccountToken: false +secrets: + - name: {{ .Values.secretName }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/server/deployment.yml b/utils/helm/speckle-server/templates/server/deployment.yml index a6a3c8644..606a90794 100644 --- a/utils/helm/speckle-server/templates/server/deployment.yml +++ b/utils/helm/speckle-server/templates/server/deployment.yml @@ -34,10 +34,22 @@ spec: cpu: {{ .Values.server.limits.cpu }} memory: {{ .Values.server.limits.memory }} - {{- if .Values.db.useCertificate }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 20000 + volumeMounts: - - name: postgres-certificate - mountPath: /postgres-certificate + - mountPath: /tmp + name: tmp + {{- if .Values.db.useCertificate }} + - name: postgres-certificate + mountPath: /postgres-certificate {{- end }} # Allow for k8s to remove the pod from the service endpoints to stop receive traffic @@ -129,6 +141,8 @@ spec: key: s3_secret_key - name: S3_CREATE_BUCKET value: "{{ .Values.s3.create_bucket }}" + - name: S3_REGION + value: "{{ .Values.s3.region }}" {{- end }} @@ -241,10 +255,37 @@ spec: name: {{ .Values.secretName }} key: apollo_key {{- end }} + {{- if .Values.server.affinity }} + affinity: {{- include "speckle.renderTpl" (dict "value" .Values.server.affinity "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.server.nodeSelector }} + nodeSelector: {{- include "speckle.renderTpl" (dict "value" .Values.server.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.server.tolerations }} + tolerations: {{- include "speckle.renderTpl" (dict "value" .Values.server.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.server.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "speckle.renderTpl" (dict "value" .Values.server.topologySpreadConstraints "context" $) | nindent 8 }} + {{- end }} priorityClassName: high-priority + + securityContext: + runAsNonRoot: true + runAsUser: 20000 + fsGroup: 25000 + fsGroupChangePolicy: OnRootMismatch + runAsGroup: 30000 + seccompProfile: + type: RuntimeDefault + + {{- if .Values.server.serviceAccount.create }} + serviceAccountName: {{ include "server.name" $ }} + {{- end }} terminationGracePeriodSeconds: 310 - {{- if .Values.db.useCertificate }} volumes: + - name: tmp + emptyDir: {} + {{- if .Values.db.useCertificate }} - name: postgres-certificate configMap: name: postgres-certificate diff --git a/utils/helm/speckle-server/templates/server/networkpolicy.yml b/utils/helm/speckle-server/templates/server/networkpolicy.yml new file mode 100644 index 000000000..7bc6ed66e --- /dev/null +++ b/utils/helm/speckle-server/templates/server/networkpolicy.yml @@ -0,0 +1,75 @@ +{{- if .Values.server.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "server.name" $ }} + namespace: {{ .Values.namespace }} + labels: +{{ include "server.labels" . | indent 4 }} +spec: + podSelector: + matchLabels: +{{ include "server.selectorLabels" . | indent 6 }} + policyTypes: + - Ingress + - Egress + egress: + # allow access to DNS + - to: + - namespaceSelector: {} + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - port: 53 + protocol: UDP +{{- if .Values.server.sentry_dns }} + # sentry.io https://docs.sentry.io/product/security/ip-ranges/#event-ingestion + - to: + - ipBlock: + cidr: 34.120.195.249/32 + ports: + - port: 443 +{{- end }} + # redis +{{ include "speckle.networkpolicy.egress.redis" $ | indent 4 }} + # postgres +{{ include "speckle.networkpolicy.egress.postgres" $ | indent 4 }} + # blob storage +{{ include "speckle.networkpolicy.egress.blob_storage" $ | indent 4 }} + ingress: + # allow ingress from the loadbalancer + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .Values.ingress.namespace }} + - podSelector: + matchLabels: + app.kubernetes.io/name: {{ .Values.ingress.controllerName }} + ports: + - port: http + # allow ingress from servicemonitor/prometheus + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ default .Values.namespace .Values.prometheusMonitoring.namespace }} + podSelector: + matchLabels: + prometheus: {{ default "kube-prometheus-stack" .Values.prometheusMonitoring.release }}-prometheus + ports: + - port: http + # allow ingress from the fileimport service + - from: + - podSelector: + matchLabels: +{{ include "fileimport_service.selectorLabels" $ | indent 14}} + ports: + - port: http +{{- if .Values.helm_test_enabled }} + # allow ingress from the test + - from: + - podSelector: + matchLabels: +{{ include "test.selectorLabels" $ | indent 14}} +{{- end }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/server/serviceaccount.yml b/utils/helm/speckle-server/templates/server/serviceaccount.yml new file mode 100644 index 000000000..2169d143b --- /dev/null +++ b/utils/helm/speckle-server/templates/server/serviceaccount.yml @@ -0,0 +1,14 @@ +{{- if .Values.server.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "server.name" $ }} + namespace: {{ .Values.namespace | quote }} + labels: +{{ include "server.labels" $ | indent 4 }} + annotations: + "kubernetes.io/enforce-mountable-secrets": "true" +automountServiceAccountToken: false +secrets: + - name: {{ .Values.secretName }} +{{- end -}} diff --git a/utils/helm/speckle-server/templates/servicemonitor.yml b/utils/helm/speckle-server/templates/servicemonitor.yml index ea0b05471..748dec68b 100644 --- a/utils/helm/speckle-server/templates/servicemonitor.yml +++ b/utils/helm/speckle-server/templates/servicemonitor.yml @@ -1,19 +1,22 @@ -{{ if .Values.enable_prometheus_monitoring }} - +{{- if .Values.enable_prometheus_monitoring }} apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: speckle-server - namespace: {{ .Values.namespace }} + namespace: {{ default .Values.namespace .Values.prometheusMonitoring.namespace }} labels: app: speckle-server - release: kube-prometheus-stack + release: {{ default "kube-prometheus-stack" .Values.prometheusMonitoring.release }} {{ include "speckle.labels" . | indent 4 }} spec: selector: matchLabels: project: speckle-server +{{- if and .Values.prometheusMonitoring.namespace (ne .Values.namespace .Values.prometheusMonitoring.namespace) }} + namespaceSelector: + matchNames: + - {{ .Values.namespace }} +{{- end }} endpoints: - port: web - -{{ end }} +{{- end }} diff --git a/utils/helm/speckle-server/templates/test/deployment.yml b/utils/helm/speckle-server/templates/test/deployment.yml index 6915fe5bf..e29205717 100644 --- a/utils/helm/speckle-server/templates/test/deployment.yml +++ b/utils/helm/speckle-server/templates/test/deployment.yml @@ -24,5 +24,26 @@ spec: limits: cpu: {{ .Values.test.limits.cpu }} memory: {{ .Values.test.limits.memory }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 20000 + restartPolicy: Never + + securityContext: + runAsNonRoot: true + runAsUser: 20000 + runAsGroup: 30000 + seccompProfile: + type: RuntimeDefault + + {{- if .Values.test.serviceAccount.create }} + serviceAccountName: {{ include "test.name" $ }} + {{- end }} {{- end }} diff --git a/utils/helm/speckle-server/templates/test/networkpolicy.yml b/utils/helm/speckle-server/templates/test/networkpolicy.yml new file mode 100644 index 000000000..be18a04e9 --- /dev/null +++ b/utils/helm/speckle-server/templates/test/networkpolicy.yml @@ -0,0 +1,36 @@ +{{- if and .Values.helm_test_enabled .Values.test.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "test.name" $ }} + namespace: {{ .Values.namespace }} + labels: +{{ include "test.labels" $ | indent 4 }} +spec: + podSelector: + matchLabels: +{{ include "test.selectorLabels" $ | indent 6 }} + policyTypes: + - Ingress + - Egress + # all ingress is blocked + ingress: [] + + egress: + # allow egress to speckle-server + - to: + - podSelector: + matchLabels: +{{ include "server.selectorLabels" $ | indent 16 }} + ports: + - port: http + # allow access to DNS + - to: + - namespaceSelector: {} + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - port: 53 + protocol: UDP +{{- end -}} diff --git a/utils/helm/speckle-server/templates/test/serviceaccount.yml b/utils/helm/speckle-server/templates/test/serviceaccount.yml new file mode 100644 index 000000000..4c2febf10 --- /dev/null +++ b/utils/helm/speckle-server/templates/test/serviceaccount.yml @@ -0,0 +1,13 @@ +{{- if .Values.test.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "test.name" $ }} + namespace: {{ .Values.namespace | quote }} + labels: +{{ include "test.labels" $ | indent 4 }} + annotations: + "kubernetes.io/enforce-mountable-secrets": "true" +automountServiceAccountToken: false +secrets: [] # does not have access to any secrets +{{- end -}} diff --git a/utils/helm/speckle-server/templates/webhook_service/deployment.yml b/utils/helm/speckle-server/templates/webhook_service/deployment.yml index 898813a1c..f669e87f8 100644 --- a/utils/helm/speckle-server/templates/webhook_service/deployment.yml +++ b/utils/helm/speckle-server/templates/webhook_service/deployment.yml @@ -43,8 +43,20 @@ spec: cpu: {{ .Values.webhook_service.limits.cpu }} memory: {{ .Values.webhook_service.limits.memory }} - {{- if .Values.db.useCertificate }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 20000 + volumeMounts: + - mountPath: /tmp + name: tmp + {{- if .Values.db.useCertificate }} - name: postgres-certificate mountPath: /postgres-certificate {{- end }} @@ -64,13 +76,40 @@ spec: value: "/postgres-certificate/ca-certificate.crt" {{- end }} + {{- if .Values.webhook_service.affinity }} + affinity: {{- include "speckle.renderTpl" (dict "value" .Values.webhook_service.affinity "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.webhook_service.nodeSelector }} + nodeSelector: {{- include "speckle.renderTpl" (dict "value" .Values.webhook_service.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.webhook_service.tolerations }} + tolerations: {{- include "speckle.renderTpl" (dict "value" .Values.webhook_service.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.webhook_service.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "speckle.renderTpl" (dict "value" .Values.webhook_service.topologySpreadConstraints "context" $) | nindent 8 }} + {{- end }} + priorityClassName: low-priority + {{- if .Values.webhook_service.serviceAccount.create }} + serviceAccountName: {{ include "webhook_service.name" $ }} + {{- end }} + + securityContext: + runAsNonRoot: true + runAsUser: 20000 + fsGroup: 25000 + fsGroupChangePolicy: OnRootMismatch + runAsGroup: 30000 + seccompProfile: + type: RuntimeDefault # Should be > webhook max call time ( ~= 10 seconds ) terminationGracePeriodSeconds: 30 - {{- if .Values.db.useCertificate }} volumes: + - name: tmp + emptyDir: {} + {{- if .Values.db.useCertificate }} - name: postgres-certificate configMap: name: postgres-certificate diff --git a/utils/helm/speckle-server/templates/webhook_service/networkpolicy.yml b/utils/helm/speckle-server/templates/webhook_service/networkpolicy.yml new file mode 100644 index 000000000..8055699c9 --- /dev/null +++ b/utils/helm/speckle-server/templates/webhook_service/networkpolicy.yml @@ -0,0 +1,42 @@ +{{- if .Values.webhook_service.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "webhook_service.name" $ }} + namespace: {{ .Values.namespace }} + labels: +{{ include "webhook_service.labels" . | indent 4 }} +spec: + podSelector: + matchLabels: +{{ include "webhook_service.selectorLabels" . | indent 6 }} + policyTypes: + - Egress + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ default .Values.namespace .Values.prometheusMonitoring.namespace }} + podSelector: + matchLabels: + prometheus: {{ default "kube-prometheus-stack" .Values.prometheusMonitoring.release }}-prometheus + ports: + - port: metrics + egress: + # webhook can call anything external, but is blocked from egress elsewhere within the cluster + - to: + - ipBlock: + cidr: 0.0.0.0/0 + # postgres +{{ include "speckle.networkpolicy.egress.postgres" $ | indent 4 }} + # allow access to DNS + - to: + - namespaceSelector: {} + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - port: 53 + protocol: UDP +{{- end -}} diff --git a/utils/helm/speckle-server/templates/webhook_service/serviceaccount.yml b/utils/helm/speckle-server/templates/webhook_service/serviceaccount.yml new file mode 100644 index 000000000..dbdce36ba --- /dev/null +++ b/utils/helm/speckle-server/templates/webhook_service/serviceaccount.yml @@ -0,0 +1,14 @@ +{{- if .Values.webhook_service.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "webhook_service.name" $ }} + namespace: {{ .Values.namespace | quote }} + labels: +{{ include "webhook_service.labels" $ | indent 4 }} + annotations: + "kubernetes.io/enforce-mountable-secrets": "true" +automountServiceAccountToken: false +secrets: + - name: {{ .Values.secretName }} +{{- end -}} diff --git a/utils/helm/speckle-server/values.schema.json b/utils/helm/speckle-server/values.schema.json new file mode 100644 index 000000000..29d3ed487 --- /dev/null +++ b/utils/helm/speckle-server/values.schema.json @@ -0,0 +1,1012 @@ +{ + "title": "Chart Values", + "type": "object", + "properties": { + "namespace": { + "type": "string", + "description": "The name of the namespace in which Speckle will be deployed.", + "default": "speckle-test" + }, + "create_namespace": { + "type": "boolean", + "description": "Enabling this will create a new namespace into which Speckle will be deployed", + "default": false + }, + "domain": { + "type": "string", + "description": "The DNS host name at which this Speckle deployment will be reachable", + "default": "localhost" + }, + "ssl_canonical_url": { + "type": "boolean", + "description": "HTTPS protocol will be the preferred protocol for serving this Speckle deployment", + "default": true + }, + "cert_manager_issuer": { + "type": "string", + "description": "The name of the ClusterIssuer kubernetes resource that provides the SSL Certificate", + "default": "letsencrypt-staging" + }, + "ingress": { + "type": "object", + "properties": { + "namespace": { + "type": "string", + "description": "The namespace in which the ingress controller is deployed.", + "default": "ingress-nginx" + }, + "controllerName": { + "type": "string", + "description": "The name of the Kubernetes pod in which the ingress controller is deployed.", + "default": "ingress-nginx" + } + } + }, + "docker_image_tag": { + "type": "string", + "description": "Speckle is published as a Docker Image. The version of the image which will be deployed is specified by this tag.", + "default": "v2.3.3" + }, + "imagePullPolicy": { + "type": "string", + "description": "Determines the conditions when the Docker Images for Speckle should be pulled from the Image registry.", + "default": "IfNotPresent" + }, + "secretName": { + "type": "string", + "description": "This is the name of the Kubernetes Secret resource in which secrets for Speckle are stored.", + "default": "server-vars" + }, + "file_size_limit_mb": { + "type": "number", + "description": "This maximum size of any single file (unit is Megabytes) that can be uploaded to Speckle", + "default": 100 + }, + "enable_prometheus_monitoring": { + "type": "boolean", + "description": "If enabled, Speckle deploys a Prometheus ServiceMonitor resource", + "default": false + }, + "prometheusMonitoring": { + "type": "object", + "properties": { + "namespace": { + "type": "string", + "description": "If provided, deploys Speckle's Prometheus resources in the given namespace", + "default": "" + }, + "release": { + "type": "string", + "description": "If provided, adds the value to a `release` label on all the Prometheus resources deployed by Speckle", + "default": "" + } + } + }, + "db": { + "type": "object", + "properties": { + "useCertificate": { + "type": "boolean", + "description": "If enabled, the certificate defined in db.certificate is used to verify TLS connections to the Postgres database", + "default": false + }, + "maxConnectionsServer": { + "type": "number", + "description": "The number of connections to the Postgres database to provide in the connection pool", + "default": 4 + }, + "certificate": { + "type": "string", + "description": "The x509 public certificate for SSL connections to the Postgres database", + "default": "" + }, + "PGSSLMODE": { + "type": "string", + "description": "This defines the level of security used when connecting to the Postgres database", + "default": "require" + }, + "networkPolicy": { + "type": "object", + "properties": { + "port": { + "type": "string", + "description": "the port on the server providing the Postgres database (default: \"5432\")", + "default": "" + }, + "externalToCluster": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, indicates that the Postgres database is hosted externally to the Kubernetes cluster", + "default": true + }, + "host": { + "type": "string", + "description": "The domain name at which the Postgres database is hosted.", + "default": "" + }, + "ipv4": { + "type": "string", + "description": "The IP address at which the Postgres database is hosted", + "default": "" + } + } + }, + "inCluster": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, indicates that the Postgres database is hosted withing the same Kubernetes cluster in which Speckle will be deployed", + "default": false + }, + "podSelector": { + "type": "object", + "description": "The pod Selector yaml object used to uniquely select the Postgres database pods within the cluster and given namespace", + "default": {} + }, + "namespaceSelector": { + "type": "object", + "description": "The namespace selector yaml object used to uniquely select the namespace in which the Postgres database pods are deployed", + "default": {} + } + } + } + } + } + } + }, + "s3": { + "type": "object", + "properties": { + "endpoint": { + "type": "string", + "description": "The URL at which the s3 compatible storage is hosted", + "default": "" + }, + "bucket": { + "type": "string", + "description": "The s3 compatible bucket in which Speckle data will be stored", + "default": "" + }, + "access_key": { + "type": "string", + "description": "The key of the access key used to authenticate with the s3 compatible storage", + "default": "" + }, + "create_bucket": { + "type": "string", + "description": "If enabled, will create a bucket with the given bucket name at this endpoint", + "default": "false" + }, + "region": { + "type": "string", + "description": "The region in which the bucket resides (or will be created in).", + "default": "" + }, + "networkPolicy": { + "type": "object", + "properties": { + "port": { + "type": "string", + "description": "the port on the server providing the s3 compatible storage (default: \"443\")", + "default": "" + }, + "externalToCluster": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, indicates that the s3 compatible storage is hosted externally to the Kubernetes cluster", + "default": true + }, + "host": { + "type": "string", + "description": "The domain name at which the s3 compatible storage is hosted.", + "default": "" + }, + "ipv4": { + "type": "string", + "description": "The IP address at which the s3 compatible storage is hosted", + "default": "" + } + } + }, + "inCluster": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, indicates that the s3 compatible storage is hosted withing the same Kubernetes cluster in which Speckle will be deployed", + "default": false + }, + "podSelector": { + "type": "object", + "description": "The pod Selector yaml object used to uniquely select the s3 compatible storage pods within the cluster and given namespace", + "default": {} + }, + "namespaceSelector": { + "type": "object", + "description": "The namespace selector yaml object used to uniquely select the namespace in which the s3 compatible storage pods are deployed", + "default": {} + } + } + } + } + } + } + }, + "redis": { + "type": "object", + "properties": { + "networkPolicy": { + "type": "object", + "properties": { + "port": { + "type": "string", + "description": "the port on the server providing the Redis store (default: \"6379\")", + "default": "" + }, + "externalToCluster": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, indicates that the Redis store is hosted externally to the Kubernetes cluster", + "default": true + }, + "host": { + "type": "string", + "description": "The domain name at which the Redis store is hosted.", + "default": "" + }, + "ipv4": { + "type": "string", + "description": "The IP address at which the Redis store is hosted", + "default": "" + } + } + }, + "inCluster": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, indicates that the Redis store is hosted withing the same Kubernetes cluster in which Speckle will be deployed", + "default": false + }, + "podSelector": { + "type": "object", + "description": "The pod Selector yaml object used to uniquely select the Redis store pods within the cluster and given namespace", + "default": {} + }, + "namespaceSelector": { + "type": "object", + "description": "The namespace selector yaml object used to uniquely select the namespace in which the Redis store pods are deployed", + "default": {} + } + } + } + } + } + } + }, + "server": { + "type": "object", + "properties": { + "replicas": { + "type": "number", + "description": "The number of instances of the Server pod to be deployed within the cluster.", + "default": 1 + }, + "auth": { + "type": "object", + "properties": { + "local": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, users can register and authenticate with an email address and password.", + "default": true + } + } + }, + "google": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, users can authenticate via Google with their Google account credentials.", + "default": false + }, + "client_id": { + "type": "string", + "description": "This is the ID for Speckle that you have registered with Google.", + "default": "" + } + } + }, + "github": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, users can authenticate via Github with their Github account credentials.", + "default": false + }, + "client_id": { + "type": "string", + "description": "This is the ID for Speckle that you have registered with Github", + "default": "" + } + } + }, + "azure_ad": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, users can authenticate via Azure Active Directory.", + "default": false + }, + "org_name": { + "type": "string", + "description": "This is the Organisation Name that you have registered with Azure", + "default": "" + }, + "identity_metadata": { + "type": "string", + "description": "This is the identity metadata for Speckle that you have registered with Azure", + "default": "" + }, + "issuer": { + "type": "string", + "description": "This is the issuer name for Speckle that you have registered with Azure", + "default": "" + }, + "client_id": { + "type": "string", + "description": "This is the ID for Speckle that you have registered with Azure", + "default": "" + } + } + } + } + }, + "email": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, Speckle can send email to users - for example, email verification for account registration.", + "default": false + }, + "host": { + "type": "string", + "description": "The domain name or IP address of the server hosting the email service.", + "default": "" + }, + "port": { + "type": "string", + "description": "The port on the server for the email service.", + "default": "" + }, + "username": { + "type": "string", + "description": "The username with which Speckle will authenticate with the email service.", + "default": "" + } + } + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The CPU that should be available on a node when scheduling this pod.", + "default": "500m" + }, + "memory": { + "type": "string", + "description": "The Memory that should be available on a node when scheduling this pod.", + "default": "1Gi" + } + } + }, + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The maximum CPU that will be made available to the server Pod in a given period.", + "default": "1000m" + }, + "memory": { + "type": "string", + "description": "The maximum Memory that will be made available to the server Pod.", + "default": "3Gi" + } + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean", + "description": "If enabled, a Kubernetes Service Account will be created for this pod.", + "default": true + } + } + }, + "monitoring": { + "type": "object", + "properties": { + "apollo": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "(Optional) If enabled, exports metrics from the GraphQL API to Apollo Graphql Studio.", + "default": false + }, + "graph_id": { + "type": "string", + "description": "The ID for Speckle that you registered in Apollo Graphql Studio.", + "default": "" + } + } + } + } + }, + "sentry_dns": { + "type": "string", + "description": "(Optional) The Data Source Name that was provided by Sentry.io", + "default": "" + }, + "disable_tracking": { + "type": "boolean", + "description": "If set to true, will prevent tracking metrics from being collected", + "default": false + }, + "disable_tracing": { + "type": "boolean", + "description": "If set to true, will prevent tracing metrics from being collected", + "default": false + }, + "networkPolicy": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports.", + "default": false + } + } + }, + "affinity": { + "type": "object", + "description": "Affinity for Speckle server pods scheduling", + "default": {} + }, + "nodeSelector": { + "type": "object", + "description": "Node labels for Speckle server pods scheduling", + "default": {} + }, + "tolerations": { + "type": "array", + "description": "Tolerations for Speckle server pods scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "topologySpreadConstraints": { + "type": "array", + "description": "Spread Constraints for Speckle server pod scheduling", + "default": [], + "items": { + "type": "object" + } + } + } + }, + "frontend": { + "type": "object", + "properties": { + "replicas": { + "type": "number", + "description": "The number of instances of the Frontend pod to be deployed within the cluster.", + "default": 1 + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The CPU that should be available on a node when scheduling this pod.", + "default": "250m" + }, + "memory": { + "type": "string", + "description": "The Memory that should be available on a node when scheduling this pod.", + "default": "256Mi" + } + } + }, + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The maximum CPU that will be made available to the frontend Pod in a given period.", + "default": "1000m" + }, + "memory": { + "type": "string", + "description": "The maximum Memory that will be made available to the frontend Pod.", + "default": "512Mi" + } + } + }, + "networkPolicy": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports.", + "default": false + } + } + }, + "affinity": { + "type": "object", + "description": "Affinity for Speckle frontend pod scheduling", + "default": {} + }, + "nodeSelector": { + "type": "object", + "description": "Node labels for Speckle frontend pods scheduling", + "default": {} + }, + "tolerations": { + "type": "array", + "description": "Tolerations for Speckle frontend pods scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "topologySpreadConstraints": { + "type": "array", + "description": "Spread Constraints for Speckle frontend pod scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean", + "description": "If enabled, a Kubernetes Service Account will be created for this pod.", + "default": true + } + } + } + } + }, + "preview_service": { + "type": "object", + "properties": { + "replicas": { + "type": "number", + "description": "The number of instances of the Preview Service pod to be deployed within the cluster.", + "default": 1 + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The CPU that should be available on a node when scheduling this pod.", + "default": "500m" + }, + "memory": { + "type": "string", + "description": "The Memory that should be available on a node when scheduling this pod.", + "default": "2Gi" + } + } + }, + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The maximum CPU that will be made available to the Preview Service Pod in a given period.", + "default": "1000m" + }, + "memory": { + "type": "string", + "description": "The maximum Memory that will be made available to the Preview Service Pod.", + "default": "4Gi" + } + } + }, + "networkPolicy": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports.", + "default": false + } + } + }, + "affinity": { + "type": "object", + "description": "Affinity for Speckle Preview Service pod scheduling", + "default": {} + }, + "nodeSelector": { + "type": "object", + "description": "Node labels for Speckle Preview Service pods scheduling", + "default": {} + }, + "tolerations": { + "type": "array", + "description": "Tolerations for Speckle Preview Service pods scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "topologySpreadConstraints": { + "type": "array", + "description": "Spread Constraints for Speckle Preview Service pod scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean", + "description": "If enabled, a Kubernetes Service Account will be created for this pod.", + "default": true + } + } + } + } + }, + "webhook_service": { + "type": "object", + "properties": { + "replicas": { + "type": "number", + "description": "The number of instances of the Webhook Service pod to be deployed within the cluster.", + "default": 1 + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The CPU that should be available on a node when scheduling this pod.", + "default": "500m" + }, + "memory": { + "type": "string", + "description": "The Memory that should be available on a node when scheduling this pod.", + "default": "2Gi" + } + } + }, + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The maximum CPU that will be made available to the Webhook Service Pod in a given period.", + "default": "1000m" + }, + "memory": { + "type": "string", + "description": "The maximum Memory that will be made available to the Webhook Service Pod.", + "default": "4Gi" + } + } + }, + "networkPolicy": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports.", + "default": false + } + } + }, + "affinity": { + "type": "object", + "description": "Affinity for Speckle Webhook Service pod scheduling", + "default": {} + }, + "nodeSelector": { + "type": "object", + "description": "Node labels for Speckle Webhook Service pods scheduling", + "default": {} + }, + "tolerations": { + "type": "array", + "description": "Tolerations for Speckle Webhook Service pods scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "topologySpreadConstraints": { + "type": "array", + "description": "Spread Constraints for Speckle Webhook Service pod scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean", + "description": "If enabled, a Kubernetes Service Account will be created for this pod.", + "default": true + } + } + } + } + }, + "fileimport_service": { + "type": "object", + "properties": { + "replicas": { + "type": "number", + "description": "The number of instances of the FileImport Service pod to be deployed within the cluster.", + "default": 1 + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The CPU that should be available on a node when scheduling this pod.", + "default": "100m" + }, + "memory": { + "type": "string", + "description": "The Memory that should be available on a node when scheduling this pod.", + "default": "512Mi" + } + } + }, + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The maximum CPU that will be made available to the FileImport Service Pod in a given period.", + "default": "1000m" + }, + "memory": { + "type": "string", + "description": "The maximum Memory that will be made available to the FileImport Service Pod.", + "default": "2Gi" + } + } + }, + "networkPolicy": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports.", + "default": false + } + } + }, + "affinity": { + "type": "object", + "description": "Affinity for Speckle FileImport Service pod scheduling", + "default": {} + }, + "nodeSelector": { + "type": "object", + "description": "Node labels for Speckle FileImport Service pods scheduling", + "default": {} + }, + "tolerations": { + "type": "array", + "description": "Tolerations for Speckle FileImport Service pods scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "topologySpreadConstraints": { + "type": "array", + "description": "Spread Constraints for Speckle FileImport Service pod scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean", + "description": "If enabled, a Kubernetes Service Account will be created for this pod.", + "default": true + } + } + }, + "time_limit_min": { + "type": "number", + "description": "The maximum time that a file can take to be processed by the FileImport Service.", + "default": 10 + } + } + }, + "monitoring": { + "type": "object", + "properties": { + "replicas": { + "type": "number", + "description": "The number of instances of the Monitoring pod to be deployed within the cluster.", + "default": 1 + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The CPU that should be available on a node when scheduling this pod.", + "default": "100m" + }, + "memory": { + "type": "string", + "description": "The Memory that should be available on a node when scheduling this pod.", + "default": "64Mi" + } + } + }, + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The maximum CPU that will be made available to the Monitoring Pod in a given period.", + "default": "200m" + }, + "memory": { + "type": "string", + "description": "The maximum Memory that will be made available to the Monitoring Pod.", + "default": "512Mi" + } + } + }, + "networkPolicy": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports.", + "default": false + } + } + }, + "affinity": { + "type": "object", + "description": "Affinity for Speckle Monitoring pod scheduling", + "default": {} + }, + "nodeSelector": { + "type": "object", + "description": "Node labels for Speckle Monitoring pods scheduling", + "default": {} + }, + "tolerations": { + "type": "array", + "description": "Tolerations for Speckle Monitoring pods scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "topologySpreadConstraints": { + "type": "array", + "description": "Spread Constraints for Speckle Monitoring pod scheduling", + "default": [], + "items": { + "type": "object" + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean", + "description": "If enabled, a Kubernetes Service Account will be created for this pod.", + "default": true + } + } + } + } + }, + "helm_test_enabled": { + "type": "boolean", + "description": "If enabled, an additional pod is deployed which verifies some functionality of Speckle to determine if it is deployed correctly", + "default": true + }, + "test": { + "type": "object", + "properties": { + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The CPU that should be available on a node when scheduling this pod.", + "default": "100m" + }, + "memory": { + "type": "string", + "description": "The Memory that should be available on a node when scheduling this pod.", + "default": "64Mi" + } + } + }, + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string", + "description": "The maximum CPU that will be made available to the Test Pod in a given period.", + "default": "200m" + }, + "memory": { + "type": "string", + "description": "The maximum Memory that will be made available to the Test Pod.", + "default": "512Mi" + } + } + }, + "networkPolicy": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports.", + "default": false + } + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean", + "description": "If enabled, a Kubernetes Service Account will be created for this pod.", + "default": true + } + } + } + } + } + } +} diff --git a/utils/helm/speckle-server/values.yaml b/utils/helm/speckle-server/values.yaml index 4ddc5d2b1..7426cf7c7 100644 --- a/utils/helm/speckle-server/values.yaml +++ b/utils/helm/speckle-server/values.yaml @@ -1,133 +1,794 @@ +## @section Namespace +## + +## @param namespace The name of the namespace in which Speckle will be deployed. +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ +## namespace: speckle-test +## @param create_namespace Enabling this will create a new namespace into which Speckle will be deployed +## The name of the namespace to create should be provided in the `namespace` parameter. +## +create_namespace: false + +## @section SSL +## + +## @param domain The DNS host name at which this Speckle deployment will be reachable +## domain: localhost + +## @param ssl_canonical_url HTTPS protocol will be the preferred protocol for serving this Speckle deployment +## ssl_canonical_url: true +## @param cert_manager_issuer The name of the ClusterIssuer kubernetes resource that provides the SSL Certificate +## +cert_manager_issuer: letsencrypt-staging + +## @section Ingress metadata for NetworkPolicy +## @descriptionStart +## This section is ignored unless networkPolicy is enabled for frontend or server. +## The NetworkPolicy uses this value to enable connections from the ingress controller pod in this namespace to reach Speckle. +## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ +## @descriptionEnd +## +ingress: + ## @param ingress.namespace The namespace in which the ingress controller is deployed. + namespace: ingress-nginx + ## @param ingress.controllerName The name of the Kubernetes pod in which the ingress controller is deployed. + controllerName: ingress-nginx + +## @section Common parameters +## +## @param docker_image_tag Speckle is published as a Docker Image. The version of the image which will be deployed is specified by this tag. +## docker_image_tag: v2.3.3 +## @param imagePullPolicy Determines the conditions when the Docker Images for Speckle should be pulled from the Image registry. +## ref: https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy +## +imagePullPolicy: IfNotPresent + +## @param secretName This is the name of the Kubernetes Secret resource in which secrets for Speckle are stored. +## Secrets within this Secret resource may include Postgres and Redis connectin strings, S3 secret values, email server passwords, etc.. +## The expected key within the Secret resource is indicated elsewhere in this values.yaml file. +## This is expected to be an opaque Secret resource type. +## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets +## +secretName: server-vars + +## @param file_size_limit_mb This maximum size of any single file (unit is Megabytes) that can be uploaded to Speckle +## +file_size_limit_mb: 100 + +## @section Monitoring +## @descriptionStart +## This enables metrics generated by Speckle to be ingested by Prometheus: https://prometheus.io/ +## Enabling this requires Prometheus to have been deployed prior, as this resource expects the Prometheus Customer Resource Definition +## for the ServiceMonitor to already be existing within the cluster. +## ref: https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/user-guides/getting-started.md#related-resources +## @descriptionEnd +## + +## @param enable_prometheus_monitoring If enabled, Speckle deploys a Prometheus ServiceMonitor resource +## +enable_prometheus_monitoring: false + +prometheusMonitoring: + ## @param prometheusMonitoring.namespace If provided, deploys Speckle's Prometheus resources in the given namespace + ## Prometheus prior to v0.19.0, or any version when deployed with default parameters, expects ServiceMonitors to be deployed within the same namespace. + ## This parameter allows the Prometheus resources provided by Speckle to be deployed in another namespace. + ## This allows Prometheus (< v0.19.0 or any version with default configuration) to be deployed in a separate namespace from Speckle. + ## Note that Speckle expect the namespace to exist prior to deployment. + ## ref: https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/user-guides/getting-started.md#related-resources + ## + namespace: '' + ## @param prometheusMonitoring.release If provided, adds the value to a `release` label on all the Prometheus resources deployed by Speckle + ## Prometheus prior to v0.19.0, or any version when deployed with default parameters, expects ServiceMonitors to be selectable on the release label. + ## This parameter allows Prometheus to be deployed with a non-default release name. + ## ref: https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/user-guides/getting-started.md#related-resources + ## + release: '' + +## @section Postgres Database +## @descriptionStart +## Defines parameters related to connections to the Postgres database. +## A secret containing the connection string to the Postgres database must stored within the Kubernetes cluster as an opaque Kubernetes Secret. +## The name of the secret must match the `secretName` parameter, and the key within this secret must be `postgres_url`. +## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets +## @descriptionEnd +## db: # postgres_url: secret -> postgres_url + ## @param db.useCertificate If enabled, the certificate defined in db.certificate is used to verify TLS connections to the Postgres database + ## useCertificate: false + ## @param db.maxConnectionsServer The number of connections to the Postgres database to provide in the connection pool + ## maxConnectionsServer: 4 + ## @param db.certificate The x509 public certificate for SSL connections to the Postgres database + ## Use of this certificate requires db.useCertificate to be enabled and an appropriate value for db.PGSSLMODE provided. + ## The value must be formatted as a multi-line string. We recommend using the pipe-symbol and taking care to + ## indent all lines of the value correctly. + ## ref: https://helm.sh/docs/chart_template_guide/yaml_techniques/#strings-in-yaml + ## certificate: '' # Multi-line string with the contents of `ca-certificate.crt` + ## @param db.PGSSLMODE This defines the level of security used when connecting to the Postgres database + ## Postgres provides different froms of protection from different types of threat when communicating between the client (Speckle) and the Postgres database. + ## ref: https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-PROTECTION + ## PGSSLMODE: require + ## @extra db.networkPolicy If networkPolicy is enabled for any service, this provides the NetworkPolicy with the necessary details to allow egress connections to the Postgres database + ## + networkPolicy: + ## @param db.networkPolicy.port the port on the server providing the Postgres database (default: "5432") + ## + port: '' + ## @extra db.networkPolicy.externalToCluster Only required if the Postgres database is not hosted within the Kubernetes cluster in which Speckle will be deployed. + ## + externalToCluster: + ## @param db.networkPolicy.externalToCluster.enabled If enabled, indicates that the Postgres database is hosted externally to the Kubernetes cluster + ## Only one of externalToCluster or inCluster should be enabled. If both are enabled then inCluster takes precedence and is the only one deployed + ## + enabled: true + ## @param db.networkPolicy.externalToCluster.host The domain name at which the Postgres database is hosted. + ## This should match the value provided within the connection string. + ## Provide the IP address if available (use the `ipv4` parameter), as the IP address takes precedence. + ## + host: '' + ## @param db.networkPolicy.externalToCluster.ipv4 The IP address at which the Postgres database is hosted + ## This should be an IP address not within the Kubernetes Cluster Pod or Service IP ranges. + ## If both host and ipv4 parameters are provided, ipv4 takes precedence and host is ignored. + ## + ipv4: '' + ## @extra db.networkPolicy.inCluster Only required if the Postgres database is hosted within the Kubernetes cluster in which Speckle will be deployed. + ## + inCluster: + ## @param db.networkPolicy.inCluster.enabled If enabled, indicates that the Postgres database is hosted withing the same Kubernetes cluster in which Speckle will be deployed + ## Only one of externalToCluster or inCluster should be enabled. If both are enabled then inCluster takes precedence and is the only set of egress network policy rules deployed. + ## + enabled: false + ## @param db.networkPolicy.inCluster.podSelector The pod Selector yaml object used to uniquely select the Postgres database pods within the cluster and given namespace + ## This is a Kubernetes podSelector object + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/#behavior-of-to-and-from-selectors + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + podSelector: {} + ## @param db.networkPolicy.inCluster.namespaceSelector The namespace selector yaml object used to uniquely select the namespace in which the Postgres database pods are deployed + ## This is a Kubernetes namespaceSelector object + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/#behavior-of-to-and-from-selectors + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + namespaceSelector: {} +## @section S3 Compatible Storage +## @descriptionStart +## Defines parameters related to connecting to the S3 compatible storage. +## A secret containing the secret key must stored within the Kubernetes cluster as an opaque Kubernetes Secret. +## The name of the Kubernetes Secret resource must match the `secretName` parameter, and the key within this Kubernetes Secret must be `s3_secret_key`. +## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets +## @descriptionEnd +## s3: + ## @param s3.endpoint The URL at which the s3 compatible storage is hosted + ## The url should be prefixed by the protocol (e.g. `https://`) + ## The url may need to include the port if it is not the default (e.g. `443` for `https` protocol) + ## endpoint: '' + ## @param s3.bucket The s3 compatible bucket in which Speckle data will be stored + ## The access key should be granted write permissions to this bucket + ## bucket: '' + ## @param s3.access_key The key of the access key used to authenticate with the s3 compatible storage + ## access_key: '' + ## @param s3.create_bucket If enabled, will create a bucket with the given bucket name at this endpoint + ## If enabled, the access_key must be granted the appropriate bucket creation privileges + ## create_bucket: 'false' - # secret_key: secret -> s3_secret_key + ## @param s3.region The region in which the bucket resides (or will be created in). + ## If not provided, defaults to `us-east-1`. For many providers of s3 compatible storage, such as minio, this value may be ignored. + ## + region: '' + ## @extra s3.networkPolicy If networkPolicy is enabled for any service, this provides the NetworkPolicy with the necessary details to allow egress connections to the s3 compatible storage + ## + networkPolicy: + ## @param s3.networkPolicy.port the port on the server providing the s3 compatible storage (default: "443") + ## + port: '' + ## @extra s3.networkPolicy.externalToCluster Only required if the s3 compatible storage is not hosted within the Kubernetes cluster in which Speckle will be deployed. + ## + externalToCluster: + ## @param s3.networkPolicy.externalToCluster.enabled If enabled, indicates that the s3 compatible storage is hosted externally to the Kubernetes cluster + ## Only one of externalToCluster or inCluster should be enabled. If both are enabled then inCluster takes precedence and is the only one deployed + ## + enabled: true + ## @param s3.networkPolicy.externalToCluster.host The domain name at which the s3 compatible storage is hosted. + ## This should match the value provided within the connection string. + ## Provide the IP address if available (use the `ipv4` parameter), as the IP address takes precedence. + ## + host: '' + ## @param s3.networkPolicy.externalToCluster.ipv4 The IP address at which the s3 compatible storage is hosted + ## This should be an IP address not within the Kubernetes Cluster Pod or Service IP ranges. + ## If both host and ipv4 parameters are provided, ipv4 takes precedence and host is ignored. + ## + ipv4: '' + ## @extra s3.networkPolicy.inCluster Only required if the s3 compatible storage is hosted within the Kubernetes cluster in which Speckle will be deployed. + ## + inCluster: + ## @param s3.networkPolicy.inCluster.enabled If enabled, indicates that the s3 compatible storage is hosted withing the same Kubernetes cluster in which Speckle will be deployed + ## Only one of externalToCluster or inCluster should be enabled. If both are enabled then inCluster takes precedence and is the only set of egress network policy rules deployed. + ## + enabled: false + ## @param s3.networkPolicy.inCluster.podSelector The pod Selector yaml object used to uniquely select the s3 compatible storage pods within the cluster and given namespace + ## This is a Kubernetes podSelector object + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/#behavior-of-to-and-from-selectors + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + podSelector: {} + ## @param s3.networkPolicy.inCluster.namespaceSelector The namespace selector yaml object used to uniquely select the namespace in which the s3 compatible storage pods are deployed + ## This is a Kubernetes namespaceSelector object + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/#behavior-of-to-and-from-selectors + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + namespaceSelector: {} -#redis: -# redis_url: secret -> redis_url +## @section Redis Store +## @descriptionStart +## Defines parameters related to connecting to the Redis Store. +## A secret containing the redis url (containing domain, username, and password) must stored within the Kubernetes cluster as an opaque Kubernetes Secret. +## The name of the Kubernetes Secret resource must match the `secretName` parameter, and the key within this Kubernetes Secret resource must be `redis_url`. +## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets +## @descriptionEnd +## +redis: + ## @extra redis.networkPolicy If networkPolicy is enabled for Speckle server, this provides the NetworkPolicy with the necessary details to allow egress connections to the Redis store + ## + networkPolicy: + ## @param redis.networkPolicy.port the port on the server providing the Redis store (default: "6379") + ## + port: '' + ## @extra redis.networkPolicy.externalToCluster Only required if the Redis store is not hosted within the Kubernetes cluster in which Speckle will be deployed. + ## + externalToCluster: + ## @param redis.networkPolicy.externalToCluster.enabled If enabled, indicates that the Redis store is hosted externally to the Kubernetes cluster + ## Only one of externalToCluster or inCluster should be enabled. If both are enabled then inCluster takes precedence and is the only one deployed + ## + enabled: true + ## @param redis.networkPolicy.externalToCluster.host The domain name at which the Redis store is hosted. + ## This should match the value provided within the connection string. + ## Provide the IP address if available (use the `ipv4` parameter), as the IP address takes precedence. + ## + host: '' + ## @param redis.networkPolicy.externalToCluster.ipv4 The IP address at which the Redis store is hosted + ## This should be an IP address not within the Kubernetes Cluster Pod or Service IP ranges. + ## If both host and ipv4 parameters are provided, ipv4 takes precedence and host is ignored. + ## + ipv4: '' + ## @extra redis.networkPolicy.inCluster is only required if the Redis store is hosted within the Kubernetes cluster in which Speckle will be deployed. + ## + inCluster: + ## @param redis.networkPolicy.inCluster.enabled If enabled, indicates that the Redis store is hosted withing the same Kubernetes cluster in which Speckle will be deployed + ## Only one of externalToCluster or inCluster should be enabled. If both are enabled then inCluster takes precedence and is the only set of egress network policy rules deployed. + ## + enabled: false + ## @param redis.networkPolicy.inCluster.podSelector The pod Selector yaml object used to uniquely select the Redis store pods within the cluster and given namespace + ## This is a Kubernetes podSelector object + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/#behavior-of-to-and-from-selectors + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + podSelector: {} + ## @param redis.networkPolicy.inCluster.namespaceSelector The namespace selector yaml object used to uniquely select the namespace in which the Redis store pods are deployed + ## This is a Kubernetes namespaceSelector object + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/#behavior-of-to-and-from-selectors + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + namespaceSelector: {} +## @section Server +## @descriptionStart +## Defines parameters related to the backend server component of Speckle. +## A secret containing the an unique value (this can be generated randomly) must stored within the Kubernetes cluster as an opaque Kubernetes Secret. +## The name of the Kubernetes Secret resource must match the `secretName` parameter, and the key within this Secret resource must be `session_secret`. +## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets +## @descriptionEnd +## server: + ## @param server.replicas The number of instances of the Server pod to be deployed within the cluster. + ## replicas: 1 - # session_secret: secret -> `session_secret` + ## @extra server.auth Speckle provides a number of different mechanisms for authenticating users. Each available option must be configured here. + ## auth: local: + ## @param server.auth.local.enabled If enabled, users can register and authenticate with an email address and password. + ## The login details are stored in the Postgres database connected to Speckle and are encrypted. + ## enabled: true google: + ## @param server.auth.google.enabled If enabled, users can authenticate via Google with their Google account credentials. + ## If enabling Google, the `server.auth.google.client_id` parameter is required. + ## A secret containing the client secret must stored within the Kubernetes cluster as an opaque Kubernetes Secret. + ## The name of the Kubernetes Secret resource must match the `secretName` parameter, and the key within this Kubernetes Secret must be `google_client_secret`. + ## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets + ## enabled: false + ## @param server.auth.google.client_id This is the ID for Speckle that you have registered with Google. + ## client_id: '' - # client_secret: secret -> `google_client_secret` github: + ## @param server.auth.github.enabled If enabled, users can authenticate via Github with their Github account credentials. + ## If enabling Github authentication, the `server.auth.github.client_id` parameter is required. + ## A secret containing the client secret must stored within the Kubernetes cluster as an opaque Kubernetes Secret. + ## The name of the Kubernetes Secret resource must match the `secretName` parameter, and the key within this Kubernetes Secret must be `github_client_secret`. + ## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets + ## enabled: false + ## @param server.auth.github.client_id This is the ID for Speckle that you have registered with Github + ## client_id: '' - # client_secret: secret -> `github_client_secret` azure_ad: + ## @param server.auth.azure_ad.enabled If enabled, users can authenticate via Azure Active Directory. + ## If enabling Azure Active Directory authentication, a secret containing the client secret must stored within the Kubernetes cluster as an opaque Kubernetes Secret. + ## The name of the Kubernetes Secret resource must match the `secretName` parameter, and the key within this Kubernetes Secret must be `azure_ad_client_secret`. + ## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets + ## enabled: false + ## @param server.auth.azure_ad.org_name This is the Organisation Name that you have registered with Azure + ## org_name: '' + ## @param server.auth.azure_ad.identity_metadata This is the identity metadata for Speckle that you have registered with Azure + ## identity_metadata: '' + ## @param server.auth.azure_ad.issuer This is the issuer name for Speckle that you have registered with Azure + ## issuer: '' + ## @param server.auth.azure_ad.client_id This is the ID for Speckle that you have registered with Azure + ## client_id: '' - # client_secret: secret -> `azure_ad_client_secret` + ## @extra server.email Speckle can communicate with users via email, providing account verification and notification. + ## email: + ## @param server.email.enabled If enabled, Speckle can send email to users - for example, email verification for account registration. + ## If enabling Email, a secret containing the password to the email server must stored within the Kubernetes cluster as an opaque Kubernetes Secret. + ## The name of the Kubernetes Secret resource must match the `secretName` parameter, and the key within this Kubernetes Secret must be `email_password`. + ## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets + ## enabled: false + ## @param server.email.host The domain name or IP address of the server hosting the email service. + ## host: '' + ## @param server.email.port The port on the server for the email service. + ## port: '' + ## @param server.email.username The username with which Speckle will authenticate with the email service. + ## Note that the `email_password` is expected to be provided in the Kubernetes Secret with the name provided in the `secretName` parameter. + ## username: '' - # password: secret -> `email_password` requests: + ## @param server.requests.cpu The CPU that should be available on a node when scheduling this pod. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 500m + ## @param server.requests.memory The Memory that should be available on a node when scheduling this pod. + ## Depending on the Kubernetes cluster's configuration, exceeding this value may result in pod eviction from a node. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 1Gi limits: + ## @param server.limits.cpu The maximum CPU that will be made available to the server Pod in a given period. + ## If this limit is exceeded, execution of the Pod will be paused until the next period. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 1000m + ## @param server.limits.memory The maximum Memory that will be made available to the server Pod. + ## If this limit is exceeded, processes within the pod that request additional memory may be stopped. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 3Gi + serviceAccount: + ## @param server.serviceAccount.create If enabled, a Kubernetes Service Account will be created for this pod. + ## This provides additional security by limiting this pod's access to the Kubernetes API and to Secrets on the Kubernetes cluster. + ## If disabled, the default Service Account will be used which in most Kubernetes configurations will grant this pod + ## access to most secrets on the cluster and access to the Kubernetes API. + ## + create: true monitoring: apollo: + ## @param server.monitoring.apollo.enabled (Optional) If enabled, exports metrics from the GraphQL API to Apollo Graphql Studio. + ## If enabling Apollo, a secret containing the key to the Apollo Graphql Studio API must stored within the Kubernetes cluster as an opaque Kubernetes Secret. + ## The name of the Kubernetes Secret resource must match the `secretName` parameter, and the key within this Kubernetes Secret must be `apollo_key`. + ## ref: https://kubernetes.io/docs/concepts/configuration/secret/#opaque-secrets + ## enabled: false + ## @param server.monitoring.apollo.graph_id The ID for Speckle that you registered in Apollo Graphql Studio. + ## graph_id: '' - # key: secret -> `apollo_key` - # Sentry specific: + ## @param server.sentry_dns (Optional) The Data Source Name that was provided by Sentry.io + ## Sentry.io allows events within Speckle to be monitored + ## sentry_dns: '' + ## @param server.disable_tracking If set to true, will prevent tracking metrics from being collected + ## Setting this value to false requires `sentry_dns` to be set + ## disable_tracking: false + ## @param server.disable_tracing If set to true, will prevent tracing metrics from being collected + ## Setting this value to false requires `sentry_dns` to be set + ## disable_tracing: false + networkPolicy: + ## @param server.networkPolicy.enabled If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports. + ## If enabled, the `ingress`, `postgres.networkPolicy`, `redis.networkPolicy`, and `s3.networkPolicy` parameters need be configured. + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ + ## + enabled: false + ## @param server.affinity Affinity for Speckle server pods scheduling + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## + affinity: {} + ## @param server.nodeSelector Node labels for Speckle server pods scheduling + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param server.tolerations Tolerations for Speckle server pods scheduling + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param server.topologySpreadConstraints Spread Constraints for Speckle server pod scheduling + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## + topologySpreadConstraints: [] +## @section Frontend +## @descriptionStart +## Defines parameters related to the frontend server component of Speckle. +## @descriptionEnd +## frontend: + ## @param frontend.replicas The number of instances of the Frontend pod to be deployed within the cluster. + ## replicas: 1 requests: + ## @param frontend.requests.cpu The CPU that should be available on a node when scheduling this pod. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 250m + ## @param frontend.requests.memory The Memory that should be available on a node when scheduling this pod. + ## Depending on the Kubernetes cluster's configuration, exceeding this value may result in pod eviction from a node. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 256Mi limits: + ## @param frontend.limits.cpu The maximum CPU that will be made available to the frontend Pod in a given period. + ## If this limit is exceeded, execution of the Pod will be paused until the next period. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 1000m + ## @param frontend.limits.memory The maximum Memory that will be made available to the frontend Pod. + ## If this limit is exceeded, processes within the pod that request additional memory may be stopped. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 512Mi + networkPolicy: + ## @param frontend.networkPolicy.enabled If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports. + ## If enabled, the `ingress` parameters need be configured. + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ + ## + enabled: false + ## @param frontend.affinity Affinity for Speckle frontend pod scheduling + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## + affinity: {} + ## @param frontend.nodeSelector Node labels for Speckle frontend pods scheduling + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param frontend.tolerations Tolerations for Speckle frontend pods scheduling + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param frontend.topologySpreadConstraints Spread Constraints for Speckle frontend pod scheduling + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## + topologySpreadConstraints: [] + serviceAccount: + ## @param frontend.serviceAccount.create If enabled, a Kubernetes Service Account will be created for this pod. + ## This provides additional security by limiting this pod's access to the Kubernetes API and to Secrets on the Kubernetes cluster. + ## If disabled, the default Service Account will be used which in most Kubernetes configurations will grant this pod + ## access to most secrets on the cluster and access to the Kubernetes API. + ## + create: true +## @section Preview Service +## @descriptionStart +## Defines parameters related to the Preview Service component of Speckle. +## @descriptionEnd +## preview_service: + ## @param preview_service.replicas The number of instances of the Preview Service pod to be deployed within the cluster. + ## replicas: 1 requests: + ## @param preview_service.requests.cpu The CPU that should be available on a node when scheduling this pod. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 500m + ## @param preview_service.requests.memory The Memory that should be available on a node when scheduling this pod. + ## Depending on the Kubernetes cluster's configuration, exceeding this value may result in pod eviction from a node. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 2Gi limits: + ## @param preview_service.limits.cpu The maximum CPU that will be made available to the Preview Service Pod in a given period. + ## If this limit is exceeded, execution of the Pod will be paused until the next period. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 1000m + ## @param preview_service.limits.memory The maximum Memory that will be made available to the Preview Service Pod. + ## If this limit is exceeded, processes within the pod that request additional memory may be stopped. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 4Gi + networkPolicy: + ## @param preview_service.networkPolicy.enabled If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports. + ## If enabled, the `db.networkPolicy` parameters need be configured. + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ + ## + enabled: false + ## @param preview_service.affinity Affinity for Speckle Preview Service pod scheduling + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## + affinity: {} + ## @param preview_service.nodeSelector Node labels for Speckle Preview Service pods scheduling + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param preview_service.tolerations Tolerations for Speckle Preview Service pods scheduling + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param preview_service.topologySpreadConstraints Spread Constraints for Speckle Preview Service pod scheduling + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## + topologySpreadConstraints: [] + serviceAccount: + ## @param preview_service.serviceAccount.create If enabled, a Kubernetes Service Account will be created for this pod. + ## This provides additional security by limiting this pod's access to the Kubernetes API and to Secrets on the Kubernetes cluster. + ## If disabled, the default Service Account will be used which in most Kubernetes configurations will grant this pod + ## access to most secrets on the cluster and access to the Kubernetes API. + ## + create: true +## @section Webhook Service +## @descriptionStart +## Defines parameters related to the Webhook Service component of Speckle. +## @descriptionEnd +## webhook_service: + ## @param webhook_service.replicas The number of instances of the Webhook Service pod to be deployed within the cluster. + ## replicas: 1 requests: - cpu: 100m - memory: 256Mi - limits: - cpu: 200m - memory: 512Mi - -fileimport_service: - replicas: 1 - requests: - cpu: 100m - memory: 512Mi - limits: - cpu: 1000m + ## @param webhook_service.requests.cpu The CPU that should be available on a node when scheduling this pod. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## + cpu: 500m + ## @param webhook_service.requests.memory The Memory that should be available on a node when scheduling this pod. + ## Depending on the Kubernetes cluster's configuration, exceeding this value may result in pod eviction from a node. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 2Gi + limits: + ## @param webhook_service.limits.cpu The maximum CPU that will be made available to the Webhook Service Pod in a given period. + ## If this limit is exceeded, execution of the Pod will be paused until the next period. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## + cpu: 1000m + ## @param webhook_service.limits.memory The maximum Memory that will be made available to the Webhook Service Pod. + ## If this limit is exceeded, processes within the pod that request additional memory may be stopped. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## + memory: 4Gi + networkPolicy: + ## @param webhook_service.networkPolicy.enabled If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports. + ## If enabled, the `db.networkPolicy` parameters need be configured. + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ + ## + enabled: false + ## @param webhook_service.affinity Affinity for Speckle Webhook Service pod scheduling + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## + affinity: {} + ## @param webhook_service.nodeSelector Node labels for Speckle Webhook Service pods scheduling + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param webhook_service.tolerations Tolerations for Speckle Webhook Service pods scheduling + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param webhook_service.topologySpreadConstraints Spread Constraints for Speckle Webhook Service pod scheduling + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## + topologySpreadConstraints: [] + serviceAccount: + ## @param webhook_service.serviceAccount.create If enabled, a Kubernetes Service Account will be created for this pod. + ## This provides additional security by limiting this pod's access to the Kubernetes API and to Secrets on the Kubernetes cluster. + ## If disabled, the default Service Account will be used which in most Kubernetes configurations will grant this pod + ## access to most secrets on the cluster and access to the Kubernetes API. + ## + create: true + +## @section File Import Service +## @descriptionStart +## Defines parameters related to the File Import Service component of Speckle. +## @descriptionEnd +## +fileimport_service: + ## @param fileimport_service.replicas The number of instances of the FileImport Service pod to be deployed within the cluster. + ## + replicas: 1 + requests: + ## @param fileimport_service.requests.cpu The CPU that should be available on a node when scheduling this pod. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## + cpu: 100m + ## @param fileimport_service.requests.memory The Memory that should be available on a node when scheduling this pod. + ## Depending on the Kubernetes cluster's configuration, exceeding this value may result in pod eviction from a node. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## + memory: 512Mi + limits: + ## @param fileimport_service.limits.cpu The maximum CPU that will be made available to the FileImport Service Pod in a given period. + ## If this limit is exceeded, execution of the Pod will be paused until the next period. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## + cpu: 1000m + ## @param fileimport_service.limits.memory The maximum Memory that will be made available to the FileImport Service Pod. + ## If this limit is exceeded, processes within the pod that request additional memory may be stopped. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## + memory: 2Gi + networkPolicy: + ## @param fileimport_service.networkPolicy.enabled If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports. + ## If enabled, the `db.networkPolicy` parameters need be configured. + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ + ## + enabled: false + ## @param fileimport_service.affinity Affinity for Speckle FileImport Service pod scheduling + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## + affinity: {} + ## @param fileimport_service.nodeSelector Node labels for Speckle FileImport Service pods scheduling + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param fileimport_service.tolerations Tolerations for Speckle FileImport Service pods scheduling + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param fileimport_service.topologySpreadConstraints Spread Constraints for Speckle FileImport Service pod scheduling + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## + topologySpreadConstraints: [] + serviceAccount: + ## @param fileimport_service.serviceAccount.create If enabled, a Kubernetes Service Account will be created for this pod. + ## This provides additional security by limiting this pod's access to the Kubernetes API and to Secrets on the Kubernetes cluster. + ## If disabled, the default Service Account will be used which in most Kubernetes configurations will grant this pod + ## access to most secrets on the cluster and access to the Kubernetes API. + ## + create: true + ## @param fileimport_service.time_limit_min The maximum time that a file can take to be processed by the FileImport Service. + ## Files which take longer than this value to process will be cancelled. + ## If you experience repeated issues with small files taking a long time, and increasing CPU and/or memory requests & limits does not help, + ## please reach out to Speckle at https://speckle.community/ + ## time_limit_min: 10 +## @section Monitoring +## @descriptionStart +## Provides Speckle with metrics related to the Postgres database. +## @descriptionEnd +## monitoring: + ## @param monitoring.replicas The number of instances of the Monitoring pod to be deployed within the cluster. + ## replicas: 1 requests: + ## @param monitoring.requests.cpu The CPU that should be available on a node when scheduling this pod. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 100m + ## @param monitoring.requests.memory The Memory that should be available on a node when scheduling this pod. + ## Depending on the Kubernetes cluster's configuration, exceeding this value may result in pod eviction from a node. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 64Mi limits: + ## @param monitoring.limits.cpu The maximum CPU that will be made available to the Monitoring Pod in a given period. + ## If this limit is exceeded, execution of the Pod will be paused until the next period. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 200m + ## @param monitoring.limits.memory The maximum Memory that will be made available to the Monitoring Pod. + ## If this limit is exceeded, processes within the pod that request additional memory may be stopped. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 512Mi + networkPolicy: + ## @param monitoring.networkPolicy.enabled If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports. + ## If enabled, the `db.networkPolicy` parameters need be configured. + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ + ## + enabled: false + ## @param monitoring.affinity Affinity for Speckle Monitoring pod scheduling + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## + affinity: {} + ## @param monitoring.nodeSelector Node labels for Speckle Monitoring pods scheduling + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param monitoring.tolerations Tolerations for Speckle Monitoring pods scheduling + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param monitoring.topologySpreadConstraints Spread Constraints for Speckle Monitoring pod scheduling + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## + topologySpreadConstraints: [] + serviceAccount: + ## @param monitoring.serviceAccount.create If enabled, a Kubernetes Service Account will be created for this pod. + ## This provides additional security by limiting this pod's access to the Kubernetes API and to Secrets on the Kubernetes cluster. + ## If disabled, the default Service Account will be used which in most Kubernetes configurations will grant this pod + ## access to most secrets on the cluster and access to the Kubernetes API. + ## + create: true + +## @section Testing +## @descriptionStart +## Defines parameters related to testing that the deployment of Speckle has been successful. +## @descriptionEnd +## + +## @param helm_test_enabled If enabled, an additional pod is deployed which verifies some functionality of Speckle to determine if it is deployed correctly +## +helm_test_enabled: true test: requests: + ## @param test.requests.cpu The CPU that should be available on a node when scheduling this pod. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 100m + ## @param test.requests.memory The Memory that should be available on a node when scheduling this pod. + ## Depending on the Kubernetes cluster's configuration, exceeding this value may result in pod eviction from a node. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 64Mi limits: + ## @param test.limits.cpu The maximum CPU that will be made available to the Test Pod in a given period. + ## If this limit is exceeded, execution of the Pod will be paused until the next period. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## cpu: 200m + ## @param test.limits.memory The maximum Memory that will be made available to the Test Pod. + ## If this limit is exceeded, processes within the pod that request additional memory may be stopped. + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## memory: 512Mi - -secretName: server-vars - -enable_prometheus_monitoring: false -cert_manager_issuer: letsencrypt-staging - -helm_test_enabled: true - -create_namespace: false -file_size_limit_mb: 100 -imagePullPolicy: IfNotPresent + networkPolicy: + ## @param test.networkPolicy.enabled If enabled, will provide additional security be limiting network traffic into and out of the pod to only the required endpoints and ports. + ## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ + ## + enabled: false + serviceAccount: + ## @param test.serviceAccount.create If enabled, a Kubernetes Service Account will be created for this pod. + ## This provides additional security by limiting this pod's access to the Kubernetes API and to Secrets on the Kubernetes cluster. + ## If disabled, the default Service Account will be used which in most Kubernetes configurations will grant this pod + ## access to most secrets on the cluster and access to the Kubernetes API. + ## + create: true diff --git a/utils/helm/update-documentation.sh b/utils/helm/update-documentation.sh new file mode 100755 index 000000000..f611fcd17 --- /dev/null +++ b/utils/helm/update-documentation.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +set -euo pipefail +if ! command -v node &> /dev/null +then + echo "πŸ›‘ node could not be found. Please install node (and ensure it is in your PATH) before trying again." + exit 1 +fi + +if ! command -v git &> /dev/null +then + echo "πŸ›‘ git could not be found. Please install git (and ensure it is in your PATH) before trying again." + exit 1 +fi + +GIT_ROOT="$(git rev-parse --show-toplevel)" + +README_GENERATOR_DIR="${GIT_ROOT}/../readme-generator-for-helm" +HELM_DIR="${GIT_ROOT}/../speckle-helm" +HELM_GIT_TARGET_BRANCH="gh-pages" +HELM_GIT_PR_BRANCH="${HELM_GIT_TARGET_BRANCH}-$(openssl rand -hex 6)" + +JSON_SCHEMA_PATH="${GIT_ROOT}/utils/helm/speckle-server/values.schema.json" + +if [ ! -d "${README_GENERATOR_DIR}" ]; then + echo "πŸ”­ Could not find readme-generator-for-helm in a sibling directory to speckle-server" + echo "πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘§ Proceeding with cloning readme-generator-for-helm to a sibling directory, readme-generator-for-helm" + git clone git@github.com:bitnami-labs/readme-generator-for-helm.git "${README_GENERATOR_DIR}" +fi + +pushd "${README_GENERATOR_DIR}" + echo "✨ Updating to the latest version of readme-generator-for-helm" + git switch main + git pull origin main +popd + +if [ ! -d "${HELM_DIR}" ]; then + echo "πŸ”­ Could not find Speckle Helm in a sibling directory (named speckle-helm) to speckle-server" + echo "πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘§ Proceeding with cloning Speckle's helm repository to a sibling directory, speckle-helm" + git clone git@github.com:specklesystems/helm.git "${HELM_DIR}" +fi + +pushd "${HELM_DIR}" + echo "✨ Updating to the latest version of Speckle helm" + git switch main + git pull origin main + echo "🍽 Preparing forked branch for updates" + git switch "${HELM_GIT_TARGET_BRANCH}" + git pull origin "${HELM_GIT_TARGET_BRANCH}" + git switch -c "${HELM_GIT_PR_BRANCH}" +popd + +pushd "${GIT_ROOT}" + echo "πŸ— Generating the documentation" + node "${README_GENERATOR_DIR}/bin/index.js" \ + --config "${GIT_ROOT}/utils/helm/.helm-readme-configuration.json" \ + --values "${GIT_ROOT}/utils/helm/speckle-server/values.yaml" \ + --readme "${HELM_DIR}/README.md" \ + --schema "${JSON_SCHEMA_PATH}" + + echo "πŸ› Workaround for bug in generator for schema.json: https://github.com/bitnami-labs/readme-generator-for-helm/issues/34" + TMP_OUTPUT="$(mktemp -t speckle-server-json-schema)" + jq --arg replacement 'object' '(.. | .items? | select(.type == "")).type |= $replacement' "${JSON_SCHEMA_PATH}" > "${TMP_OUTPUT}" && mv "${TMP_OUTPUT}" "${JSON_SCHEMA_PATH}" +popd + +pushd "${HELM_DIR}" + echo "🌳 Preparing Pull Request for Helm README..." + git add README.md + git commit -m "Updating README with revised parameters from values.yaml" + git push --set-upstream origin "${HELM_GIT_PR_BRANCH}" + echo "πŸ™ Please create a Pull Request, ❗️selecting gh-pages as the target branch❗️: https://github.com/specklesystems/helm/pull/new/${HELM_GIT_PR_BRANCH}" +popd