mirror of
https://github.com/Infisical/infisical.git
synced 2025-09-04 07:35:30 +00:00
Compare commits
2 Commits
infisical/
...
sid/fix/re
Author | SHA1 | Date | |
---|---|---|---|
|
f304b25be1 | ||
|
d68d00ebc0 |
13
.env.example
13
.env.example
@@ -123,17 +123,8 @@ INF_APP_CONNECTION_GITHUB_RADAR_APP_WEBHOOK_SECRET=
|
|||||||
INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL=
|
INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL=
|
||||||
|
|
||||||
# azure app connection
|
# azure app connection
|
||||||
INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID=
|
INF_APP_CONNECTION_AZURE_CLIENT_ID=
|
||||||
INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET=
|
INF_APP_CONNECTION_AZURE_CLIENT_SECRET=
|
||||||
|
|
||||||
INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID=
|
|
||||||
INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET=
|
|
||||||
|
|
||||||
INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID=
|
|
||||||
INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET=
|
|
||||||
|
|
||||||
INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID=
|
|
||||||
INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET=
|
|
||||||
|
|
||||||
# datadog
|
# datadog
|
||||||
SHOULD_USE_DATADOG_TRACER=
|
SHOULD_USE_DATADOG_TRACER=
|
||||||
|
153
.github/workflows/release_build_infisical_cli.yml
vendored
Normal file
153
.github/workflows/release_build_infisical_cli.yml
vendored
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
name: Build and release CLI
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
push:
|
||||||
|
# run only against tags
|
||||||
|
tags:
|
||||||
|
- "infisical-cli/v*.*.*"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cli-integration-tests:
|
||||||
|
name: Run tests before deployment
|
||||||
|
uses: ./.github/workflows/run-cli-tests.yml
|
||||||
|
secrets:
|
||||||
|
CLI_TESTS_UA_CLIENT_ID: ${{ secrets.CLI_TESTS_UA_CLIENT_ID }}
|
||||||
|
CLI_TESTS_UA_CLIENT_SECRET: ${{ secrets.CLI_TESTS_UA_CLIENT_SECRET }}
|
||||||
|
CLI_TESTS_SERVICE_TOKEN: ${{ secrets.CLI_TESTS_SERVICE_TOKEN }}
|
||||||
|
CLI_TESTS_PROJECT_ID: ${{ secrets.CLI_TESTS_PROJECT_ID }}
|
||||||
|
CLI_TESTS_ENV_SLUG: ${{ secrets.CLI_TESTS_ENV_SLUG }}
|
||||||
|
CLI_TESTS_USER_EMAIL: ${{ secrets.CLI_TESTS_USER_EMAIL }}
|
||||||
|
CLI_TESTS_USER_PASSWORD: ${{ secrets.CLI_TESTS_USER_PASSWORD }}
|
||||||
|
CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE: ${{ secrets.CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE }}
|
||||||
|
|
||||||
|
npm-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
working-directory: ./npm
|
||||||
|
needs:
|
||||||
|
- cli-integration-tests
|
||||||
|
- goreleaser
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Extract version
|
||||||
|
run: |
|
||||||
|
VERSION=$(echo ${{ github.ref_name }} | sed 's/infisical-cli\/v//')
|
||||||
|
echo "Version extracted: $VERSION"
|
||||||
|
echo "CLI_VERSION=$VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Print version
|
||||||
|
run: echo ${{ env.CLI_VERSION }}
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: "npm"
|
||||||
|
cache-dependency-path: ./npm/package-lock.json
|
||||||
|
- name: Install dependencies
|
||||||
|
working-directory: ${{ env.working-directory }}
|
||||||
|
run: npm install --ignore-scripts
|
||||||
|
|
||||||
|
- name: Set NPM version
|
||||||
|
working-directory: ${{ env.working-directory }}
|
||||||
|
run: npm version ${{ env.CLI_VERSION }} --allow-same-version --no-git-tag-version
|
||||||
|
|
||||||
|
- name: Setup NPM
|
||||||
|
working-directory: ${{ env.working-directory }}
|
||||||
|
run: |
|
||||||
|
echo 'registry="https://registry.npmjs.org/"' > ./.npmrc
|
||||||
|
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ./.npmrc
|
||||||
|
|
||||||
|
echo 'registry="https://registry.npmjs.org/"' > ~/.npmrc
|
||||||
|
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
|
||||||
|
env:
|
||||||
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
|
- name: Pack NPM
|
||||||
|
working-directory: ${{ env.working-directory }}
|
||||||
|
run: npm pack
|
||||||
|
|
||||||
|
- name: Publish NPM
|
||||||
|
working-directory: ${{ env.working-directory }}
|
||||||
|
run: npm publish --tarball=./infisical-sdk-${{github.ref_name}} --access public --registry=https://registry.npmjs.org/
|
||||||
|
env:
|
||||||
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
|
goreleaser:
|
||||||
|
runs-on: ubuntu-latest-8-cores
|
||||||
|
needs: [cli-integration-tests]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: 🐋 Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: 🔧 Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- run: git fetch --force --tags
|
||||||
|
- run: echo "Ref name ${{github.ref_name}}"
|
||||||
|
- uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ">=1.19.3"
|
||||||
|
cache: true
|
||||||
|
cache-dependency-path: cli/go.sum
|
||||||
|
- name: Setup for libssl1.0-dev
|
||||||
|
run: |
|
||||||
|
echo 'deb http://security.ubuntu.com/ubuntu bionic-security main' | sudo tee -a /etc/apt/sources.list
|
||||||
|
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
|
||||||
|
sudo apt update
|
||||||
|
sudo apt-get install -y libssl1.0-dev
|
||||||
|
- name: OSXCross for CGO Support
|
||||||
|
run: |
|
||||||
|
mkdir ../../osxcross
|
||||||
|
git clone https://github.com/plentico/osxcross-target.git ../../osxcross/target
|
||||||
|
- uses: goreleaser/goreleaser-action@v4
|
||||||
|
with:
|
||||||
|
distribution: goreleaser-pro
|
||||||
|
version: v1.26.2-pro
|
||||||
|
args: release --clean
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }}
|
||||||
|
POSTHOG_API_KEY_FOR_CLI: ${{ secrets.POSTHOG_API_KEY_FOR_CLI }}
|
||||||
|
FURY_TOKEN: ${{ secrets.FURYPUSHTOKEN }}
|
||||||
|
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||||
|
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
- run: pip install --upgrade cloudsmith-cli
|
||||||
|
- uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252
|
||||||
|
with:
|
||||||
|
ruby-version: "3.3" # Not needed with a .ruby-version, .tool-versions or mise.toml
|
||||||
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
||||||
|
- name: Install deb-s3
|
||||||
|
run: gem install deb-s3
|
||||||
|
- name: Configure GPG Key
|
||||||
|
run: echo -n "$GPG_SIGNING_KEY" | base64 --decode | gpg --batch --import
|
||||||
|
env:
|
||||||
|
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
|
||||||
|
GPG_SIGNING_KEY_PASSPHRASE: ${{ secrets.GPG_SIGNING_KEY_PASSPHRASE }}
|
||||||
|
- name: Publish to CloudSmith
|
||||||
|
run: sh cli/upload_to_cloudsmith.sh
|
||||||
|
env:
|
||||||
|
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
||||||
|
INFISICAL_CLI_S3_BUCKET: ${{ secrets.INFISICAL_CLI_S3_BUCKET }}
|
||||||
|
INFISICAL_CLI_REPO_SIGNING_KEY_ID: ${{ secrets.INFISICAL_CLI_REPO_SIGNING_KEY_ID }}
|
||||||
|
AWS_ACCESS_KEY_ID: ${{ secrets.INFISICAL_CLI_REPO_AWS_ACCESS_KEY_ID }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.INFISICAL_CLI_REPO_AWS_SECRET_ACCESS_KEY }}
|
||||||
|
- name: Invalidate Cloudfront cache
|
||||||
|
run: aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths '/deb/dists/stable/*'
|
||||||
|
env:
|
||||||
|
AWS_ACCESS_KEY_ID: ${{ secrets.INFISICAL_CLI_REPO_AWS_ACCESS_KEY_ID }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.INFISICAL_CLI_REPO_AWS_SECRET_ACCESS_KEY }}
|
||||||
|
CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.INFISICAL_CLI_REPO_CLOUDFRONT_DISTRIBUTION_ID }}
|
55
.github/workflows/run-cli-tests.yml
vendored
Normal file
55
.github/workflows/run-cli-tests.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
name: Go CLI Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize]
|
||||||
|
paths:
|
||||||
|
- "cli/**"
|
||||||
|
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
workflow_call:
|
||||||
|
secrets:
|
||||||
|
CLI_TESTS_UA_CLIENT_ID:
|
||||||
|
required: true
|
||||||
|
CLI_TESTS_UA_CLIENT_SECRET:
|
||||||
|
required: true
|
||||||
|
CLI_TESTS_SERVICE_TOKEN:
|
||||||
|
required: true
|
||||||
|
CLI_TESTS_PROJECT_ID:
|
||||||
|
required: true
|
||||||
|
CLI_TESTS_ENV_SLUG:
|
||||||
|
required: true
|
||||||
|
CLI_TESTS_USER_EMAIL:
|
||||||
|
required: true
|
||||||
|
CLI_TESTS_USER_PASSWORD:
|
||||||
|
required: true
|
||||||
|
CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE:
|
||||||
|
required: true
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./cli
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: "1.21.x"
|
||||||
|
- name: Install dependencies
|
||||||
|
run: go get .
|
||||||
|
- name: Test with the Go CLI
|
||||||
|
env:
|
||||||
|
CLI_TESTS_UA_CLIENT_ID: ${{ secrets.CLI_TESTS_UA_CLIENT_ID }}
|
||||||
|
CLI_TESTS_UA_CLIENT_SECRET: ${{ secrets.CLI_TESTS_UA_CLIENT_SECRET }}
|
||||||
|
CLI_TESTS_SERVICE_TOKEN: ${{ secrets.CLI_TESTS_SERVICE_TOKEN }}
|
||||||
|
CLI_TESTS_PROJECT_ID: ${{ secrets.CLI_TESTS_PROJECT_ID }}
|
||||||
|
CLI_TESTS_ENV_SLUG: ${{ secrets.CLI_TESTS_ENV_SLUG }}
|
||||||
|
CLI_TESTS_USER_EMAIL: ${{ secrets.CLI_TESTS_USER_EMAIL }}
|
||||||
|
CLI_TESTS_USER_PASSWORD: ${{ secrets.CLI_TESTS_USER_PASSWORD }}
|
||||||
|
# INFISICAL_VAULT_FILE_PASSPHRASE: ${{ secrets.CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE }}
|
||||||
|
|
||||||
|
run: go test -v -count=1 ./test
|
241
.goreleaser.yaml
Normal file
241
.goreleaser.yaml
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||||
|
# Make sure to check the documentation at https://goreleaser.com
|
||||||
|
# before:
|
||||||
|
# hooks:
|
||||||
|
# # You may remove this if you don't use go modules.
|
||||||
|
# - cd cli && go mod tidy
|
||||||
|
# # you may remove this if you don't need go generate
|
||||||
|
# - cd cli && go generate ./...
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
- ./cli/scripts/completions.sh
|
||||||
|
- ./cli/scripts/manpages.sh
|
||||||
|
|
||||||
|
monorepo:
|
||||||
|
tag_prefix: infisical-cli/
|
||||||
|
dir: cli
|
||||||
|
|
||||||
|
builds:
|
||||||
|
- id: darwin-build
|
||||||
|
binary: infisical
|
||||||
|
ldflags:
|
||||||
|
- -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
|
||||||
|
- -X github.com/Infisical/infisical-merge/packages/telemetry.POSTHOG_API_KEY_FOR_CLI={{ .Env.POSTHOG_API_KEY_FOR_CLI }}
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
- CC=/home/runner/work/osxcross/target/bin/o64-clang
|
||||||
|
- CXX=/home/runner/work/osxcross/target/bin/o64-clang++
|
||||||
|
goos:
|
||||||
|
- darwin
|
||||||
|
ignore:
|
||||||
|
- goos: darwin
|
||||||
|
goarch: "386"
|
||||||
|
dir: ./cli
|
||||||
|
|
||||||
|
- id: all-other-builds
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
binary: infisical
|
||||||
|
ldflags:
|
||||||
|
- -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
|
||||||
|
- -X github.com/Infisical/infisical-merge/packages/telemetry.POSTHOG_API_KEY_FOR_CLI={{ .Env.POSTHOG_API_KEY_FOR_CLI }}
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
|
goos:
|
||||||
|
- freebsd
|
||||||
|
- linux
|
||||||
|
- netbsd
|
||||||
|
- openbsd
|
||||||
|
- windows
|
||||||
|
goarch:
|
||||||
|
- "386"
|
||||||
|
- amd64
|
||||||
|
- arm
|
||||||
|
- arm64
|
||||||
|
goarm:
|
||||||
|
- "6"
|
||||||
|
- "7"
|
||||||
|
ignore:
|
||||||
|
- goos: windows
|
||||||
|
goarch: "386"
|
||||||
|
- goos: freebsd
|
||||||
|
goarch: "386"
|
||||||
|
dir: ./cli
|
||||||
|
|
||||||
|
archives:
|
||||||
|
- format_overrides:
|
||||||
|
- goos: windows
|
||||||
|
format: zip
|
||||||
|
files:
|
||||||
|
- ../README*
|
||||||
|
- ../LICENSE*
|
||||||
|
- ../manpages/*
|
||||||
|
- ../completions/*
|
||||||
|
|
||||||
|
release:
|
||||||
|
replace_existing_draft: true
|
||||||
|
mode: "replace"
|
||||||
|
|
||||||
|
checksum:
|
||||||
|
name_template: "checksums.txt"
|
||||||
|
|
||||||
|
snapshot:
|
||||||
|
name_template: "{{ .Version }}-devel"
|
||||||
|
|
||||||
|
# publishers:
|
||||||
|
# - name: fury.io
|
||||||
|
# ids:
|
||||||
|
# - infisical
|
||||||
|
# dir: "{{ dir .ArtifactPath }}"
|
||||||
|
# cmd: curl -F package=@{{ .ArtifactName }} https://{{ .Env.FURY_TOKEN }}@push.fury.io/infisical/
|
||||||
|
|
||||||
|
brews:
|
||||||
|
- name: infisical
|
||||||
|
tap:
|
||||||
|
owner: Infisical
|
||||||
|
name: homebrew-get-cli
|
||||||
|
commit_author:
|
||||||
|
name: "Infisical"
|
||||||
|
email: ai@infisical.com
|
||||||
|
folder: Formula
|
||||||
|
homepage: "https://infisical.com"
|
||||||
|
description: "The official Infisical CLI"
|
||||||
|
install: |-
|
||||||
|
bin.install "infisical"
|
||||||
|
bash_completion.install "completions/infisical.bash" => "infisical"
|
||||||
|
zsh_completion.install "completions/infisical.zsh" => "_infisical"
|
||||||
|
fish_completion.install "completions/infisical.fish"
|
||||||
|
man1.install "manpages/infisical.1.gz"
|
||||||
|
- name: "infisical@{{.Version}}"
|
||||||
|
tap:
|
||||||
|
owner: Infisical
|
||||||
|
name: homebrew-get-cli
|
||||||
|
commit_author:
|
||||||
|
name: "Infisical"
|
||||||
|
email: ai@infisical.com
|
||||||
|
folder: Formula
|
||||||
|
homepage: "https://infisical.com"
|
||||||
|
description: "The official Infisical CLI"
|
||||||
|
install: |-
|
||||||
|
bin.install "infisical"
|
||||||
|
bash_completion.install "completions/infisical.bash" => "infisical"
|
||||||
|
zsh_completion.install "completions/infisical.zsh" => "_infisical"
|
||||||
|
fish_completion.install "completions/infisical.fish"
|
||||||
|
man1.install "manpages/infisical.1.gz"
|
||||||
|
|
||||||
|
nfpms:
|
||||||
|
- id: infisical
|
||||||
|
package_name: infisical
|
||||||
|
builds:
|
||||||
|
- all-other-builds
|
||||||
|
vendor: Infisical, Inc
|
||||||
|
homepage: https://infisical.com/
|
||||||
|
maintainer: Infisical, Inc
|
||||||
|
description: The offical Infisical CLI
|
||||||
|
license: MIT
|
||||||
|
formats:
|
||||||
|
- rpm
|
||||||
|
- deb
|
||||||
|
- apk
|
||||||
|
- archlinux
|
||||||
|
bindir: /usr/bin
|
||||||
|
contents:
|
||||||
|
- src: ./completions/infisical.bash
|
||||||
|
dst: /etc/bash_completion.d/infisical
|
||||||
|
- src: ./completions/infisical.fish
|
||||||
|
dst: /usr/share/fish/vendor_completions.d/infisical.fish
|
||||||
|
- src: ./completions/infisical.zsh
|
||||||
|
dst: /usr/share/zsh/site-functions/_infisical
|
||||||
|
- src: ./manpages/infisical.1.gz
|
||||||
|
dst: /usr/share/man/man1/infisical.1.gz
|
||||||
|
|
||||||
|
scoop:
|
||||||
|
bucket:
|
||||||
|
owner: Infisical
|
||||||
|
name: scoop-infisical
|
||||||
|
commit_author:
|
||||||
|
name: "Infisical"
|
||||||
|
email: ai@infisical.com
|
||||||
|
homepage: "https://infisical.com"
|
||||||
|
description: "The official Infisical CLI"
|
||||||
|
license: MIT
|
||||||
|
|
||||||
|
winget:
|
||||||
|
- name: infisical
|
||||||
|
publisher: infisical
|
||||||
|
license: MIT
|
||||||
|
homepage: https://infisical.com
|
||||||
|
short_description: "The official Infisical CLI"
|
||||||
|
repository:
|
||||||
|
owner: infisical
|
||||||
|
name: winget-pkgs
|
||||||
|
branch: "infisical-{{.Version}}"
|
||||||
|
pull_request:
|
||||||
|
enabled: true
|
||||||
|
draft: false
|
||||||
|
base:
|
||||||
|
owner: microsoft
|
||||||
|
name: winget-pkgs
|
||||||
|
branch: master
|
||||||
|
|
||||||
|
aurs:
|
||||||
|
- name: infisical-bin
|
||||||
|
homepage: "https://infisical.com"
|
||||||
|
description: "The official Infisical CLI"
|
||||||
|
maintainers:
|
||||||
|
- Infisical, Inc <support@infisical.com>
|
||||||
|
license: MIT
|
||||||
|
private_key: "{{ .Env.AUR_KEY }}"
|
||||||
|
git_url: "ssh://aur@aur.archlinux.org/infisical-bin.git"
|
||||||
|
package: |-
|
||||||
|
# bin
|
||||||
|
install -Dm755 "./infisical" "${pkgdir}/usr/bin/infisical"
|
||||||
|
# license
|
||||||
|
install -Dm644 "./LICENSE" "${pkgdir}/usr/share/licenses/infisical/LICENSE"
|
||||||
|
# completions
|
||||||
|
mkdir -p "${pkgdir}/usr/share/bash-completion/completions/"
|
||||||
|
mkdir -p "${pkgdir}/usr/share/zsh/site-functions/"
|
||||||
|
mkdir -p "${pkgdir}/usr/share/fish/vendor_completions.d/"
|
||||||
|
install -Dm644 "./completions/infisical.bash" "${pkgdir}/usr/share/bash-completion/completions/infisical"
|
||||||
|
install -Dm644 "./completions/infisical.zsh" "${pkgdir}/usr/share/zsh/site-functions/_infisical"
|
||||||
|
install -Dm644 "./completions/infisical.fish" "${pkgdir}/usr/share/fish/vendor_completions.d/infisical.fish"
|
||||||
|
# man pages
|
||||||
|
install -Dm644 "./manpages/infisical.1.gz" "${pkgdir}/usr/share/man/man1/infisical.1.gz"
|
||||||
|
|
||||||
|
dockers:
|
||||||
|
- dockerfile: docker/alpine
|
||||||
|
goos: linux
|
||||||
|
goarch: amd64
|
||||||
|
use: buildx
|
||||||
|
ids:
|
||||||
|
- all-other-builds
|
||||||
|
image_templates:
|
||||||
|
- "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-amd64"
|
||||||
|
- "infisical/cli:latest-amd64"
|
||||||
|
build_flag_templates:
|
||||||
|
- "--pull"
|
||||||
|
- "--platform=linux/amd64"
|
||||||
|
- dockerfile: docker/alpine
|
||||||
|
goos: linux
|
||||||
|
goarch: amd64
|
||||||
|
use: buildx
|
||||||
|
ids:
|
||||||
|
- all-other-builds
|
||||||
|
image_templates:
|
||||||
|
- "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-arm64"
|
||||||
|
- "infisical/cli:latest-arm64"
|
||||||
|
build_flag_templates:
|
||||||
|
- "--pull"
|
||||||
|
- "--platform=linux/arm64"
|
||||||
|
|
||||||
|
docker_manifests:
|
||||||
|
- name_template: "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}"
|
||||||
|
image_templates:
|
||||||
|
- "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-amd64"
|
||||||
|
- "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-arm64"
|
||||||
|
- name_template: "infisical/cli:latest"
|
||||||
|
image_templates:
|
||||||
|
- "infisical/cli:latest-amd64"
|
||||||
|
- "infisical/cli:latest-arm64"
|
@@ -34,8 +34,6 @@ ENV VITE_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
|
|||||||
ARG CAPTCHA_SITE_KEY
|
ARG CAPTCHA_SITE_KEY
|
||||||
ENV VITE_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
|
ENV VITE_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
|
||||||
|
|
||||||
ENV NODE_OPTIONS="--max-old-space-size=8192"
|
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
@@ -147,11 +145,7 @@ RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \
|
|||||||
&& cd openssl-3.1.2 \
|
&& cd openssl-3.1.2 \
|
||||||
&& ./Configure enable-fips \
|
&& ./Configure enable-fips \
|
||||||
&& make \
|
&& make \
|
||||||
&& make install_fips \
|
&& make install_fips
|
||||||
&& cd / \
|
|
||||||
&& rm -rf /openssl-build \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
|
||||||
|
|
||||||
# Install Infisical CLI
|
# Install Infisical CLI
|
||||||
RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash \
|
RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash \
|
||||||
@@ -192,11 +186,12 @@ ENV NODE_ENV production
|
|||||||
ENV STANDALONE_BUILD true
|
ENV STANDALONE_BUILD true
|
||||||
ENV STANDALONE_MODE true
|
ENV STANDALONE_MODE true
|
||||||
ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/
|
ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/
|
||||||
ENV NODE_OPTIONS="--max-old-space-size=8192 --force-fips"
|
ENV NODE_OPTIONS="--max-old-space-size=1024"
|
||||||
|
|
||||||
# FIPS mode of operation:
|
# FIPS mode of operation:
|
||||||
ENV OPENSSL_CONF=/backend/nodejs.fips.cnf
|
ENV OPENSSL_CONF=/backend/nodejs.fips.cnf
|
||||||
ENV OPENSSL_MODULES=/usr/local/lib/ossl-modules
|
ENV OPENSSL_MODULES=/usr/local/lib/ossl-modules
|
||||||
|
ENV NODE_OPTIONS=--force-fips
|
||||||
ENV FIPS_ENABLED=true
|
ENV FIPS_ENABLED=true
|
||||||
|
|
||||||
|
|
||||||
@@ -211,11 +206,6 @@ EXPOSE 443
|
|||||||
RUN grep -v 'import "./lib/telemetry/instrumentation.mjs";' dist/main.mjs > dist/main.mjs.tmp && \
|
RUN grep -v 'import "./lib/telemetry/instrumentation.mjs";' dist/main.mjs > dist/main.mjs.tmp && \
|
||||||
mv dist/main.mjs.tmp dist/main.mjs
|
mv dist/main.mjs.tmp dist/main.mjs
|
||||||
|
|
||||||
# The OpenSSL library is installed in different locations in different architectures (x86_64 and arm64).
|
|
||||||
# This is a workaround to avoid errors when the library is not found.
|
|
||||||
RUN ln -sf /usr/local/lib64/ossl-modules /usr/local/lib/ossl-modules || \
|
|
||||||
ln -sf /usr/local/lib/ossl-modules /usr/local/lib64/ossl-modules
|
|
||||||
|
|
||||||
USER non-root-user
|
USER non-root-user
|
||||||
|
|
||||||
CMD ["./standalone-entrypoint.sh"]
|
CMD ["./standalone-entrypoint.sh"]
|
@@ -55,8 +55,6 @@ USER non-root-user
|
|||||||
##
|
##
|
||||||
FROM base AS backend-build
|
FROM base AS backend-build
|
||||||
|
|
||||||
ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install all required dependencies for build
|
# Install all required dependencies for build
|
||||||
@@ -86,8 +84,6 @@ RUN npm run build
|
|||||||
# Production stage
|
# Production stage
|
||||||
FROM base AS backend-runner
|
FROM base AS backend-runner
|
||||||
|
|
||||||
ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install all required dependencies for runtime
|
# Install all required dependencies for runtime
|
||||||
@@ -116,11 +112,6 @@ RUN mkdir frontend-build
|
|||||||
FROM base AS production
|
FROM base AS production
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
build-essential \
|
|
||||||
autoconf \
|
|
||||||
automake \
|
|
||||||
libtool \
|
|
||||||
libssl-dev \
|
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
bash \
|
bash \
|
||||||
curl \
|
curl \
|
||||||
@@ -180,7 +171,6 @@ ENV NODE_ENV production
|
|||||||
ENV STANDALONE_BUILD true
|
ENV STANDALONE_BUILD true
|
||||||
ENV STANDALONE_MODE true
|
ENV STANDALONE_MODE true
|
||||||
ENV NODE_OPTIONS="--max-old-space-size=1024"
|
ENV NODE_OPTIONS="--max-old-space-size=1024"
|
||||||
ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/
|
|
||||||
|
|
||||||
WORKDIR /backend
|
WORKDIR /backend
|
||||||
|
|
||||||
|
11
README.md
11
README.md
@@ -149,8 +149,11 @@ Not sure where to get started? You can:
|
|||||||
|
|
||||||
- Join our <a href="https://infisical.com/slack">Slack</a>, and ask us any questions there.
|
- Join our <a href="https://infisical.com/slack">Slack</a>, and ask us any questions there.
|
||||||
|
|
||||||
## We are hiring!
|
## Resources
|
||||||
|
|
||||||
If you're reading this, there is a strong chance you like the products we created.
|
- [Docs](https://infisical.com/docs/documentation/getting-started/introduction) for comprehensive documentation and guides
|
||||||
|
- [Slack](https://infisical.com/slack) for discussion with the community and Infisical team.
|
||||||
You might also make a great addition to our team. We're growing fast and would love for you to [join us](https://infisical.com/careers).
|
- [GitHub](https://github.com/Infisical/infisical) for code, issues, and pull requests
|
||||||
|
- [Twitter](https://twitter.com/infisical) for fast news
|
||||||
|
- [YouTube](https://www.youtube.com/@infisical_os) for videos on secret management
|
||||||
|
- [Blog](https://infisical.com/blog) for secret management insights, articles, tutorials, and updates
|
||||||
|
@@ -59,11 +59,7 @@ RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \
|
|||||||
&& cd openssl-3.1.2 \
|
&& cd openssl-3.1.2 \
|
||||||
&& ./Configure enable-fips \
|
&& ./Configure enable-fips \
|
||||||
&& make \
|
&& make \
|
||||||
&& make install_fips \
|
&& make install_fips
|
||||||
&& cd / \
|
|
||||||
&& rm -rf /openssl-build \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
|
||||||
|
|
||||||
# ? App setup
|
# ? App setup
|
||||||
|
|
||||||
|
@@ -24,7 +24,6 @@ export const mockQueue = (): TQueueServiceFactory => {
|
|||||||
events[name] = event;
|
events[name] = event;
|
||||||
},
|
},
|
||||||
getRepeatableJobs: async () => [],
|
getRepeatableJobs: async () => [],
|
||||||
getDelayedJobs: async () => [],
|
|
||||||
clearQueue: async () => {},
|
clearQueue: async () => {},
|
||||||
stopJobById: async () => {},
|
stopJobById: async () => {},
|
||||||
stopJobByIdPg: async () => {},
|
stopJobByIdPg: async () => {},
|
||||||
|
84
backend/package-lock.json
generated
84
backend/package-lock.json
generated
@@ -33,7 +33,7 @@
|
|||||||
"@gitbeaker/rest": "^42.5.0",
|
"@gitbeaker/rest": "^42.5.0",
|
||||||
"@google-cloud/kms": "^4.5.0",
|
"@google-cloud/kms": "^4.5.0",
|
||||||
"@infisical/quic": "^1.0.8",
|
"@infisical/quic": "^1.0.8",
|
||||||
"@node-saml/passport-saml": "^5.1.0",
|
"@node-saml/passport-saml": "^5.0.1",
|
||||||
"@octokit/auth-app": "^7.1.1",
|
"@octokit/auth-app": "^7.1.1",
|
||||||
"@octokit/core": "^5.2.1",
|
"@octokit/core": "^5.2.1",
|
||||||
"@octokit/plugin-paginate-graphql": "^4.0.1",
|
"@octokit/plugin-paginate-graphql": "^4.0.1",
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
"ajv": "^8.12.0",
|
"ajv": "^8.12.0",
|
||||||
"argon2": "^0.31.2",
|
"argon2": "^0.31.2",
|
||||||
"aws-sdk": "^2.1553.0",
|
"aws-sdk": "^2.1553.0",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.6.7",
|
||||||
"axios-retry": "^4.0.0",
|
"axios-retry": "^4.0.0",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"botbuilder": "^4.23.2",
|
"botbuilder": "^4.23.2",
|
||||||
@@ -9573,20 +9573,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@node-saml/node-saml": {
|
"node_modules/@node-saml/node-saml": {
|
||||||
"version": "5.1.0",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@node-saml/node-saml/-/node-saml-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@node-saml/node-saml/-/node-saml-5.0.1.tgz",
|
||||||
"integrity": "sha512-t3cJnZ4aC7HhPZ6MGylGZULvUtBOZ6FzuUndaHGXjmIZHXnLfC/7L8a57O9Q9V7AxJGKAiRM5zu2wNm9EsvQpw==",
|
"integrity": "sha512-YQzFPEC+CnsfO9AFYnwfYZKIzOLx3kITaC1HrjHVLTo6hxcQhc+LgHODOMvW4VCV95Gwrz1MshRUWCPzkDqmnA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/debug": "^4.1.12",
|
"@types/debug": "^4.1.12",
|
||||||
"@types/qs": "^6.9.18",
|
"@types/qs": "^6.9.11",
|
||||||
"@types/xml-encryption": "^1.2.4",
|
"@types/xml-encryption": "^1.2.4",
|
||||||
"@types/xml2js": "^0.4.14",
|
"@types/xml2js": "^0.4.14",
|
||||||
"@xmldom/is-dom-node": "^1.0.1",
|
"@xmldom/is-dom-node": "^1.0.1",
|
||||||
"@xmldom/xmldom": "^0.8.10",
|
"@xmldom/xmldom": "^0.8.10",
|
||||||
"debug": "^4.4.0",
|
"debug": "^4.3.4",
|
||||||
"xml-crypto": "^6.1.2",
|
"xml-crypto": "^6.0.1",
|
||||||
"xml-encryption": "^3.1.0",
|
"xml-encryption": "^3.0.2",
|
||||||
"xml2js": "^0.6.2",
|
"xml2js": "^0.6.2",
|
||||||
"xmlbuilder": "^15.1.1",
|
"xmlbuilder": "^15.1.1",
|
||||||
"xpath": "^0.0.34"
|
"xpath": "^0.0.34"
|
||||||
@@ -9596,9 +9596,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@node-saml/node-saml/node_modules/debug": {
|
"node_modules/@node-saml/node-saml/node_modules/debug": {
|
||||||
"version": "4.4.1",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@@ -9635,14 +9635,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@node-saml/passport-saml": {
|
"node_modules/@node-saml/passport-saml": {
|
||||||
"version": "5.1.0",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@node-saml/passport-saml/-/passport-saml-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@node-saml/passport-saml/-/passport-saml-5.0.1.tgz",
|
||||||
"integrity": "sha512-pBm+iFjv9eihcgeJuSUs4c0AuX1QEFdHwP8w1iaWCfDzXdeWZxUBU5HT2bY2S4dvNutcy+A9hYsH7ZLBGtgwDg==",
|
"integrity": "sha512-fMztg3zfSnjLEgxvpl6HaDMNeh0xeQX4QHiF9e2Lsie2dc4qFE37XYbQZhVmn8XJ2awPpSWLQ736UskYgGU8lQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@node-saml/node-saml": "^5.1.0",
|
"@node-saml/node-saml": "^5.0.1",
|
||||||
"@types/express": "^4.17.23",
|
"@types/express": "^4.17.21",
|
||||||
"@types/passport": "^1.0.17",
|
"@types/passport": "^1.0.16",
|
||||||
"@types/passport-strategy": "^0.2.38",
|
"@types/passport-strategy": "^0.2.38",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"passport-strategy": "^1.0.0"
|
"passport-strategy": "^1.0.0"
|
||||||
@@ -13350,10 +13350,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/express": {
|
"node_modules/@types/express": {
|
||||||
"version": "4.17.23",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz",
|
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
|
||||||
"integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==",
|
"integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/body-parser": "*",
|
"@types/body-parser": "*",
|
||||||
"@types/express-serve-static-core": "^4.17.33",
|
"@types/express-serve-static-core": "^4.17.33",
|
||||||
@@ -13523,10 +13522,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/passport": {
|
"node_modules/@types/passport": {
|
||||||
"version": "1.0.17",
|
"version": "1.0.16",
|
||||||
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz",
|
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.16.tgz",
|
||||||
"integrity": "sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==",
|
"integrity": "sha512-FD0qD5hbPWQzaM0wHUnJ/T0BBCJBxCeemtnCwc/ThhTg3x9jfrAcRUmj5Dopza+MfFS9acTe3wk7rcVnRIp/0A==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/express": "*"
|
"@types/express": "*"
|
||||||
}
|
}
|
||||||
@@ -13701,16 +13699,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/request/node_modules/form-data": {
|
"node_modules/@types/request/node_modules/form-data": {
|
||||||
"version": "2.5.5",
|
"version": "2.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz",
|
||||||
"integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==",
|
"integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.6",
|
||||||
"es-set-tostringtag": "^2.1.0",
|
"mime-types": "^2.1.12",
|
||||||
"hasown": "^2.0.2",
|
|
||||||
"mime-types": "^2.1.35",
|
|
||||||
"safe-buffer": "^5.2.1"
|
"safe-buffer": "^5.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -15234,13 +15230,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.11.0",
|
"version": "1.7.9",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
||||||
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
|
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
"form-data": "^4.0.4",
|
"form-data": "^4.0.0",
|
||||||
"proxy-from-env": "^1.1.0"
|
"proxy-from-env": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -18765,15 +18761,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
"es-set-tostringtag": "^2.1.0",
|
"es-set-tostringtag": "^2.1.0",
|
||||||
"hasown": "^2.0.2",
|
|
||||||
"mime-types": "^2.1.12"
|
"mime-types": "^2.1.12"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -31954,9 +31948,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/xml-crypto": {
|
"node_modules/xml-crypto": {
|
||||||
"version": "6.1.2",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-6.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-6.0.1.tgz",
|
||||||
"integrity": "sha512-leBOVQdVi8FvPJrMYoum7Ici9qyxfE4kVi+AkpUoYCSXaQF4IlBm1cneTK9oAxR61LpYxTx7lNcsnBIeRpGW2w==",
|
"integrity": "sha512-v05aU7NS03z4jlZ0iZGRFeZsuKO1UfEbbYiaeRMiATBFs6Jq9+wqKquEMTn4UTrYZ9iGD8yz3KT4L9o2iF682w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xmldom/is-dom-node": "^1.0.1",
|
"@xmldom/is-dom-node": "^1.0.1",
|
||||||
|
@@ -153,7 +153,7 @@
|
|||||||
"@gitbeaker/rest": "^42.5.0",
|
"@gitbeaker/rest": "^42.5.0",
|
||||||
"@google-cloud/kms": "^4.5.0",
|
"@google-cloud/kms": "^4.5.0",
|
||||||
"@infisical/quic": "^1.0.8",
|
"@infisical/quic": "^1.0.8",
|
||||||
"@node-saml/passport-saml": "^5.1.0",
|
"@node-saml/passport-saml": "^5.0.1",
|
||||||
"@octokit/auth-app": "^7.1.1",
|
"@octokit/auth-app": "^7.1.1",
|
||||||
"@octokit/core": "^5.2.1",
|
"@octokit/core": "^5.2.1",
|
||||||
"@octokit/plugin-paginate-graphql": "^4.0.1",
|
"@octokit/plugin-paginate-graphql": "^4.0.1",
|
||||||
@@ -181,7 +181,7 @@
|
|||||||
"ajv": "^8.12.0",
|
"ajv": "^8.12.0",
|
||||||
"argon2": "^0.31.2",
|
"argon2": "^0.31.2",
|
||||||
"aws-sdk": "^2.1553.0",
|
"aws-sdk": "^2.1553.0",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.6.7",
|
||||||
"axios-retry": "^4.0.0",
|
"axios-retry": "^4.0.0",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"botbuilder": "^4.23.2",
|
"botbuilder": "^4.23.2",
|
||||||
|
15
backend/src/@types/fastify.d.ts
vendored
15
backend/src/@types/fastify.d.ts
vendored
@@ -12,8 +12,6 @@ import { TCertificateAuthorityCrlServiceFactory } from "@app/ee/services/certifi
|
|||||||
import { TCertificateEstServiceFactory } from "@app/ee/services/certificate-est/certificate-est-service";
|
import { TCertificateEstServiceFactory } from "@app/ee/services/certificate-est/certificate-est-service";
|
||||||
import { TDynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-types";
|
import { TDynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-types";
|
||||||
import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-types";
|
import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-types";
|
||||||
import { TEventBusService } from "@app/ee/services/event/event-bus-service";
|
|
||||||
import { TServerSentEventsService } from "@app/ee/services/event/event-sse-service";
|
|
||||||
import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
|
import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
|
||||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||||
import { TGithubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service";
|
import { TGithubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service";
|
||||||
@@ -95,7 +93,6 @@ import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env
|
|||||||
import { TProjectKeyServiceFactory } from "@app/services/project-key/project-key-service";
|
import { TProjectKeyServiceFactory } from "@app/services/project-key/project-key-service";
|
||||||
import { TProjectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service";
|
import { TProjectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service";
|
||||||
import { TProjectRoleServiceFactory } from "@app/services/project-role/project-role-service";
|
import { TProjectRoleServiceFactory } from "@app/services/project-role/project-role-service";
|
||||||
import { TReminderServiceFactory } from "@app/services/reminder/reminder-types";
|
|
||||||
import { TSecretServiceFactory } from "@app/services/secret/secret-service";
|
import { TSecretServiceFactory } from "@app/services/secret/secret-service";
|
||||||
import { TSecretBlindIndexServiceFactory } from "@app/services/secret-blind-index/secret-blind-index-service";
|
import { TSecretBlindIndexServiceFactory } from "@app/services/secret-blind-index/secret-blind-index-service";
|
||||||
import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service";
|
import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service";
|
||||||
@@ -128,15 +125,6 @@ declare module "@fastify/request-context" {
|
|||||||
namespace: string;
|
namespace: string;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
aws?: {
|
|
||||||
accountId: string;
|
|
||||||
arn: string;
|
|
||||||
userId: string;
|
|
||||||
partition: string;
|
|
||||||
service: string;
|
|
||||||
resourceType: string;
|
|
||||||
resourceName: string;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
identityPermissionMetadata?: Record<string, unknown>; // filled by permission service
|
identityPermissionMetadata?: Record<string, unknown>; // filled by permission service
|
||||||
assumedPrivilegeDetails?: { requesterId: string; actorId: string; actorType: ActorType; projectId: string };
|
assumedPrivilegeDetails?: { requesterId: string; actorId: string; actorType: ActorType; projectId: string };
|
||||||
@@ -297,9 +285,6 @@ declare module "fastify" {
|
|||||||
secretScanningV2: TSecretScanningV2ServiceFactory;
|
secretScanningV2: TSecretScanningV2ServiceFactory;
|
||||||
internalCertificateAuthority: TInternalCertificateAuthorityServiceFactory;
|
internalCertificateAuthority: TInternalCertificateAuthorityServiceFactory;
|
||||||
pkiTemplate: TPkiTemplatesServiceFactory;
|
pkiTemplate: TPkiTemplatesServiceFactory;
|
||||||
reminder: TReminderServiceFactory;
|
|
||||||
bus: TEventBusService;
|
|
||||||
sse: TServerSentEventsService;
|
|
||||||
};
|
};
|
||||||
// this is exclusive use for middlewares in which we need to inject data
|
// this is exclusive use for middlewares in which we need to inject data
|
||||||
// everywhere else access using service layer
|
// everywhere else access using service layer
|
||||||
|
33
backend/src/@types/knex.d.ts
vendored
33
backend/src/@types/knex.d.ts
vendored
@@ -489,11 +489,6 @@ import {
|
|||||||
TWorkflowIntegrationsInsert,
|
TWorkflowIntegrationsInsert,
|
||||||
TWorkflowIntegrationsUpdate
|
TWorkflowIntegrationsUpdate
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import {
|
|
||||||
TAccessApprovalPoliciesEnvironments,
|
|
||||||
TAccessApprovalPoliciesEnvironmentsInsert,
|
|
||||||
TAccessApprovalPoliciesEnvironmentsUpdate
|
|
||||||
} from "@app/db/schemas/access-approval-policies-environments";
|
|
||||||
import {
|
import {
|
||||||
TIdentityLdapAuths,
|
TIdentityLdapAuths,
|
||||||
TIdentityLdapAuthsInsert,
|
TIdentityLdapAuthsInsert,
|
||||||
@@ -509,17 +504,6 @@ import {
|
|||||||
TProjectMicrosoftTeamsConfigsInsert,
|
TProjectMicrosoftTeamsConfigsInsert,
|
||||||
TProjectMicrosoftTeamsConfigsUpdate
|
TProjectMicrosoftTeamsConfigsUpdate
|
||||||
} from "@app/db/schemas/project-microsoft-teams-configs";
|
} from "@app/db/schemas/project-microsoft-teams-configs";
|
||||||
import { TReminders, TRemindersInsert, TRemindersUpdate } from "@app/db/schemas/reminders";
|
|
||||||
import {
|
|
||||||
TRemindersRecipients,
|
|
||||||
TRemindersRecipientsInsert,
|
|
||||||
TRemindersRecipientsUpdate
|
|
||||||
} from "@app/db/schemas/reminders-recipients";
|
|
||||||
import {
|
|
||||||
TSecretApprovalPoliciesEnvironments,
|
|
||||||
TSecretApprovalPoliciesEnvironmentsInsert,
|
|
||||||
TSecretApprovalPoliciesEnvironmentsUpdate
|
|
||||||
} from "@app/db/schemas/secret-approval-policies-environments";
|
|
||||||
import {
|
import {
|
||||||
TSecretReminderRecipients,
|
TSecretReminderRecipients,
|
||||||
TSecretReminderRecipientsInsert,
|
TSecretReminderRecipientsInsert,
|
||||||
@@ -897,12 +881,6 @@ declare module "knex/types/tables" {
|
|||||||
TAccessApprovalPoliciesBypassersUpdate
|
TAccessApprovalPoliciesBypassersUpdate
|
||||||
>;
|
>;
|
||||||
|
|
||||||
[TableName.AccessApprovalPolicyEnvironment]: KnexOriginal.CompositeTableType<
|
|
||||||
TAccessApprovalPoliciesEnvironments,
|
|
||||||
TAccessApprovalPoliciesEnvironmentsInsert,
|
|
||||||
TAccessApprovalPoliciesEnvironmentsUpdate
|
|
||||||
>;
|
|
||||||
|
|
||||||
[TableName.AccessApprovalRequest]: KnexOriginal.CompositeTableType<
|
[TableName.AccessApprovalRequest]: KnexOriginal.CompositeTableType<
|
||||||
TAccessApprovalRequests,
|
TAccessApprovalRequests,
|
||||||
TAccessApprovalRequestsInsert,
|
TAccessApprovalRequestsInsert,
|
||||||
@@ -951,11 +929,6 @@ declare module "knex/types/tables" {
|
|||||||
TSecretApprovalRequestSecretTagsInsert,
|
TSecretApprovalRequestSecretTagsInsert,
|
||||||
TSecretApprovalRequestSecretTagsUpdate
|
TSecretApprovalRequestSecretTagsUpdate
|
||||||
>;
|
>;
|
||||||
[TableName.SecretApprovalPolicyEnvironment]: KnexOriginal.CompositeTableType<
|
|
||||||
TSecretApprovalPoliciesEnvironments,
|
|
||||||
TSecretApprovalPoliciesEnvironmentsInsert,
|
|
||||||
TSecretApprovalPoliciesEnvironmentsUpdate
|
|
||||||
>;
|
|
||||||
[TableName.SecretRotation]: KnexOriginal.CompositeTableType<
|
[TableName.SecretRotation]: KnexOriginal.CompositeTableType<
|
||||||
TSecretRotations,
|
TSecretRotations,
|
||||||
TSecretRotationsInsert,
|
TSecretRotationsInsert,
|
||||||
@@ -1238,11 +1211,5 @@ declare module "knex/types/tables" {
|
|||||||
TSecretScanningConfigsInsert,
|
TSecretScanningConfigsInsert,
|
||||||
TSecretScanningConfigsUpdate
|
TSecretScanningConfigsUpdate
|
||||||
>;
|
>;
|
||||||
[TableName.Reminder]: KnexOriginal.CompositeTableType<TReminders, TRemindersInsert, TRemindersUpdate>;
|
|
||||||
[TableName.ReminderRecipient]: KnexOriginal.CompositeTableType<
|
|
||||||
TRemindersRecipients,
|
|
||||||
TRemindersRecipientsInsert,
|
|
||||||
TRemindersRecipientsUpdate
|
|
||||||
>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,43 +0,0 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import { TableName } from "../schemas";
|
|
||||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
if (!(await knex.schema.hasTable(TableName.Reminder))) {
|
|
||||||
await knex.schema.createTable(TableName.Reminder, (t) => {
|
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
|
||||||
t.uuid("secretId").nullable();
|
|
||||||
t.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE");
|
|
||||||
t.string("message", 1024).nullable();
|
|
||||||
t.integer("repeatDays").checkPositive().nullable();
|
|
||||||
t.timestamp("nextReminderDate").notNullable();
|
|
||||||
t.timestamps(true, true, true);
|
|
||||||
t.unique("secretId");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await knex.schema.hasTable(TableName.ReminderRecipient))) {
|
|
||||||
await knex.schema.createTable(TableName.ReminderRecipient, (t) => {
|
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
|
||||||
t.uuid("reminderId").notNullable();
|
|
||||||
t.foreign("reminderId").references("id").inTable(TableName.Reminder).onDelete("CASCADE");
|
|
||||||
t.uuid("userId").notNullable();
|
|
||||||
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
|
||||||
t.timestamps(true, true, true);
|
|
||||||
t.index("reminderId");
|
|
||||||
t.index("userId");
|
|
||||||
t.unique(["reminderId", "userId"]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await createOnUpdateTrigger(knex, TableName.Reminder);
|
|
||||||
await createOnUpdateTrigger(knex, TableName.ReminderRecipient);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
await dropOnUpdateTrigger(knex, TableName.Reminder);
|
|
||||||
await dropOnUpdateTrigger(knex, TableName.ReminderRecipient);
|
|
||||||
await knex.schema.dropTableIfExists(TableName.ReminderRecipient);
|
|
||||||
await knex.schema.dropTableIfExists(TableName.Reminder);
|
|
||||||
}
|
|
@@ -1,19 +0,0 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import { TableName } from "../schemas";
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
if (!(await knex.schema.hasColumn(TableName.AppConnection, "gatewayId"))) {
|
|
||||||
await knex.schema.alterTable(TableName.AppConnection, (t) => {
|
|
||||||
t.uuid("gatewayId").nullable();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
if (await knex.schema.hasColumn(TableName.AppConnection, "gatewayId")) {
|
|
||||||
await knex.schema.alterTable(TableName.AppConnection, (t) => {
|
|
||||||
t.dropColumn("gatewayId");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,432 +0,0 @@
|
|||||||
import slugify from "@sindresorhus/slugify";
|
|
||||||
import { Knex } from "knex";
|
|
||||||
import { v4 as uuidV4 } from "uuid";
|
|
||||||
|
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
|
||||||
|
|
||||||
import { ProjectType, TableName } from "../schemas";
|
|
||||||
|
|
||||||
/* eslint-disable no-await-in-loop,@typescript-eslint/ban-ts-comment */
|
|
||||||
|
|
||||||
// Single query to get all projects that need any kind of kickout
|
|
||||||
const getProjectsNeedingKickouts = async (
|
|
||||||
knex: Knex
|
|
||||||
): Promise<
|
|
||||||
Array<{
|
|
||||||
id: string;
|
|
||||||
defaultProduct: string;
|
|
||||||
needsSecretManager: boolean;
|
|
||||||
needsCertManager: boolean;
|
|
||||||
needsSecretScanning: boolean;
|
|
||||||
needsKms: boolean;
|
|
||||||
needsSsh: boolean;
|
|
||||||
}>
|
|
||||||
> => {
|
|
||||||
const result = await knex.raw(
|
|
||||||
`
|
|
||||||
SELECT DISTINCT
|
|
||||||
p.id,
|
|
||||||
p."defaultProduct",
|
|
||||||
|
|
||||||
-- Use CASE with direct joins instead of EXISTS subqueries
|
|
||||||
CASE WHEN p."defaultProduct" != 'secret-manager' AND s.secret_exists IS NOT NULL THEN true ELSE false END AS "needsSecretManager",
|
|
||||||
CASE WHEN p."defaultProduct" != 'cert-manager' AND ca.ca_exists IS NOT NULL THEN true ELSE false END AS "needsCertManager",
|
|
||||||
CASE WHEN p."defaultProduct" != 'secret-scanning' AND ssds.ssds_exists IS NOT NULL THEN true ELSE false END AS "needsSecretScanning",
|
|
||||||
CASE WHEN p."defaultProduct" != 'kms' AND kk.kms_exists IS NOT NULL THEN true ELSE false END AS "needsKms",
|
|
||||||
CASE WHEN p."defaultProduct" != 'ssh' AND sc.ssh_exists IS NOT NULL THEN true ELSE false END AS "needsSsh"
|
|
||||||
|
|
||||||
FROM projects p
|
|
||||||
LEFT JOIN (
|
|
||||||
SELECT DISTINCT e."projectId", 1 as secret_exists
|
|
||||||
FROM secrets_v2 s
|
|
||||||
JOIN secret_folders sf ON sf.id = s."folderId"
|
|
||||||
JOIN project_environments e ON e.id = sf."envId"
|
|
||||||
) s ON s."projectId" = p.id AND p."defaultProduct" != 'secret-manager'
|
|
||||||
|
|
||||||
LEFT JOIN (
|
|
||||||
SELECT DISTINCT "projectId", 1 as ca_exists
|
|
||||||
FROM certificate_authorities
|
|
||||||
) ca ON ca."projectId" = p.id AND p."defaultProduct" != 'cert-manager'
|
|
||||||
|
|
||||||
LEFT JOIN (
|
|
||||||
SELECT DISTINCT "projectId", 1 as ssds_exists
|
|
||||||
FROM secret_scanning_data_sources
|
|
||||||
) ssds ON ssds."projectId" = p.id AND p."defaultProduct" != 'secret-scanning'
|
|
||||||
|
|
||||||
LEFT JOIN (
|
|
||||||
SELECT DISTINCT "projectId", 1 as kms_exists
|
|
||||||
FROM kms_keys
|
|
||||||
WHERE "isReserved" = false
|
|
||||||
) kk ON kk."projectId" = p.id AND p."defaultProduct" != 'kms'
|
|
||||||
|
|
||||||
LEFT JOIN (
|
|
||||||
SELECT DISTINCT sca."projectId", 1 as ssh_exists
|
|
||||||
FROM ssh_certificates sc
|
|
||||||
JOIN ssh_certificate_authorities sca ON sca.id = sc."sshCaId"
|
|
||||||
) sc ON sc."projectId" = p.id AND p."defaultProduct" != 'ssh'
|
|
||||||
|
|
||||||
WHERE p."defaultProduct" IS NOT NULL
|
|
||||||
AND (
|
|
||||||
(p."defaultProduct" != 'secret-manager' AND s.secret_exists IS NOT NULL) OR
|
|
||||||
(p."defaultProduct" != 'cert-manager' AND ca.ca_exists IS NOT NULL) OR
|
|
||||||
(p."defaultProduct" != 'secret-scanning' AND ssds.ssds_exists IS NOT NULL) OR
|
|
||||||
(p."defaultProduct" != 'kms' AND kk.kms_exists IS NOT NULL) OR
|
|
||||||
(p."defaultProduct" != 'ssh' AND sc.ssh_exists IS NOT NULL)
|
|
||||||
)
|
|
||||||
`
|
|
||||||
);
|
|
||||||
|
|
||||||
return result.rows;
|
|
||||||
};
|
|
||||||
|
|
||||||
const newProject = async (knex: Knex, projectId: string, projectType: ProjectType) => {
|
|
||||||
const newProjectId = uuidV4();
|
|
||||||
const project = await knex(TableName.Project).where("id", projectId).first();
|
|
||||||
await knex(TableName.Project).insert({
|
|
||||||
...project,
|
|
||||||
type: projectType,
|
|
||||||
defaultProduct: null,
|
|
||||||
// @ts-ignore id is required
|
|
||||||
id: newProjectId,
|
|
||||||
slug: slugify(`${project?.name}-${alphaNumericNanoId(8)}`)
|
|
||||||
});
|
|
||||||
|
|
||||||
const customRoleMapping: Record<string, string> = {};
|
|
||||||
const projectCustomRoles = await knex(TableName.ProjectRoles).where("projectId", projectId);
|
|
||||||
if (projectCustomRoles.length) {
|
|
||||||
await knex.batchInsert(
|
|
||||||
TableName.ProjectRoles,
|
|
||||||
projectCustomRoles.map((el) => {
|
|
||||||
const id = uuidV4();
|
|
||||||
customRoleMapping[el.id] = id;
|
|
||||||
return {
|
|
||||||
...el,
|
|
||||||
id,
|
|
||||||
projectId: newProjectId,
|
|
||||||
permissions: el.permissions ? JSON.stringify(el.permissions) : el.permissions
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const groupMembershipMapping: Record<string, string> = {};
|
|
||||||
const groupMemberships = await knex(TableName.GroupProjectMembership).where("projectId", projectId);
|
|
||||||
if (groupMemberships.length) {
|
|
||||||
await knex.batchInsert(
|
|
||||||
TableName.GroupProjectMembership,
|
|
||||||
groupMemberships.map((el) => {
|
|
||||||
const id = uuidV4();
|
|
||||||
groupMembershipMapping[el.id] = id;
|
|
||||||
return { ...el, id, projectId: newProjectId };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const groupMembershipRoles = await knex(TableName.GroupProjectMembershipRole).whereIn(
|
|
||||||
"projectMembershipId",
|
|
||||||
groupMemberships.map((el) => el.id)
|
|
||||||
);
|
|
||||||
if (groupMembershipRoles.length) {
|
|
||||||
await knex.batchInsert(
|
|
||||||
TableName.GroupProjectMembershipRole,
|
|
||||||
groupMembershipRoles.map((el) => {
|
|
||||||
const id = uuidV4();
|
|
||||||
const projectMembershipId = groupMembershipMapping[el.projectMembershipId];
|
|
||||||
const customRoleId = el.customRoleId ? customRoleMapping[el.customRoleId] : el.customRoleId;
|
|
||||||
return { ...el, id, projectMembershipId, customRoleId };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const identityProjectMembershipMapping: Record<string, string> = {};
|
|
||||||
const identities = await knex(TableName.IdentityProjectMembership).where("projectId", projectId);
|
|
||||||
if (identities.length) {
|
|
||||||
await knex.batchInsert(
|
|
||||||
TableName.IdentityProjectMembership,
|
|
||||||
identities.map((el) => {
|
|
||||||
const id = uuidV4();
|
|
||||||
identityProjectMembershipMapping[el.id] = id;
|
|
||||||
return { ...el, id, projectId: newProjectId };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const identitiesRoles = await knex(TableName.IdentityProjectMembershipRole).whereIn(
|
|
||||||
"projectMembershipId",
|
|
||||||
identities.map((el) => el.id)
|
|
||||||
);
|
|
||||||
if (identitiesRoles.length) {
|
|
||||||
await knex.batchInsert(
|
|
||||||
TableName.IdentityProjectMembershipRole,
|
|
||||||
identitiesRoles.map((el) => {
|
|
||||||
const id = uuidV4();
|
|
||||||
const projectMembershipId = identityProjectMembershipMapping[el.projectMembershipId];
|
|
||||||
const customRoleId = el.customRoleId ? customRoleMapping[el.customRoleId] : el.customRoleId;
|
|
||||||
return { ...el, id, projectMembershipId, customRoleId };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectMembershipMapping: Record<string, string> = {};
|
|
||||||
const projectUserMembers = await knex(TableName.ProjectMembership).where("projectId", projectId);
|
|
||||||
if (projectUserMembers.length) {
|
|
||||||
await knex.batchInsert(
|
|
||||||
TableName.ProjectMembership,
|
|
||||||
projectUserMembers.map((el) => {
|
|
||||||
const id = uuidV4();
|
|
||||||
projectMembershipMapping[el.id] = id;
|
|
||||||
return { ...el, id, projectId: newProjectId };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const membershipRoles = await knex(TableName.ProjectUserMembershipRole).whereIn(
|
|
||||||
"projectMembershipId",
|
|
||||||
projectUserMembers.map((el) => el.id)
|
|
||||||
);
|
|
||||||
if (membershipRoles.length) {
|
|
||||||
await knex.batchInsert(
|
|
||||||
TableName.ProjectUserMembershipRole,
|
|
||||||
membershipRoles.map((el) => {
|
|
||||||
const id = uuidV4();
|
|
||||||
const projectMembershipId = projectMembershipMapping[el.projectMembershipId];
|
|
||||||
const customRoleId = el.customRoleId ? customRoleMapping[el.customRoleId] : el.customRoleId;
|
|
||||||
return { ...el, id, projectMembershipId, customRoleId };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const kmsKeys = await knex(TableName.KmsKey).where("projectId", projectId).andWhere("isReserved", true);
|
|
||||||
if (kmsKeys.length) {
|
|
||||||
await knex.batchInsert(
|
|
||||||
TableName.KmsKey,
|
|
||||||
kmsKeys.map((el) => {
|
|
||||||
const id = uuidV4();
|
|
||||||
const slug = slugify(alphaNumericNanoId(8).toLowerCase());
|
|
||||||
return { ...el, id, slug, projectId: newProjectId };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectBot = await knex(TableName.ProjectBot).where("projectId", projectId).first();
|
|
||||||
if (projectBot) {
|
|
||||||
const newProjectBot = { ...projectBot, id: uuidV4(), projectId: newProjectId };
|
|
||||||
await knex(TableName.ProjectBot).insert(newProjectBot);
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectKeys = await knex(TableName.ProjectKeys).where("projectId", projectId);
|
|
||||||
if (projectKeys.length) {
|
|
||||||
await knex.batchInsert(
|
|
||||||
TableName.ProjectKeys,
|
|
||||||
projectKeys.map((el) => {
|
|
||||||
const id = uuidV4();
|
|
||||||
return { ...el, id, projectId: newProjectId };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectGateways = await knex(TableName.ProjectGateway).where("projectId", projectId);
|
|
||||||
if (projectGateways.length) {
|
|
||||||
await knex.batchInsert(
|
|
||||||
TableName.ProjectGateway,
|
|
||||||
projectGateways.map((el) => {
|
|
||||||
const id = uuidV4();
|
|
||||||
return { ...el, id, projectId: newProjectId };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectSlackConfigs = await knex(TableName.ProjectSlackConfigs).where("projectId", projectId);
|
|
||||||
if (projectSlackConfigs.length) {
|
|
||||||
await knex.batchInsert(
|
|
||||||
TableName.ProjectSlackConfigs,
|
|
||||||
projectSlackConfigs.map((el) => {
|
|
||||||
const id = uuidV4();
|
|
||||||
return { ...el, id, projectId: newProjectId };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectMicrosoftTeamsConfigs = await knex(TableName.ProjectMicrosoftTeamsConfigs).where("projectId", projectId);
|
|
||||||
if (projectMicrosoftTeamsConfigs.length) {
|
|
||||||
await knex.batchInsert(
|
|
||||||
TableName.ProjectMicrosoftTeamsConfigs,
|
|
||||||
projectMicrosoftTeamsConfigs.map((el) => {
|
|
||||||
const id = uuidV4();
|
|
||||||
return { ...el, id, projectId: newProjectId };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const trustedIps = await knex(TableName.TrustedIps).where("projectId", projectId);
|
|
||||||
if (trustedIps.length) {
|
|
||||||
await knex.batchInsert(
|
|
||||||
TableName.TrustedIps,
|
|
||||||
trustedIps.map((el) => {
|
|
||||||
const id = uuidV4();
|
|
||||||
return { ...el, id, projectId: newProjectId };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newProjectId;
|
|
||||||
};
|
|
||||||
|
|
||||||
const kickOutSecretManagerProject = async (knex: Knex, oldProjectId: string) => {
|
|
||||||
const newProjectId = await newProject(knex, oldProjectId, ProjectType.SecretManager);
|
|
||||||
await knex(TableName.IntegrationAuth).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
await knex(TableName.Environment).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
await knex(TableName.SecretBlindIndex).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
await knex(TableName.SecretSync).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
await knex(TableName.SecretTag).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
await knex(TableName.SecretReminderRecipients).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
await knex(TableName.ServiceToken).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const kickOutCertManagerProject = async (knex: Knex, oldProjectId: string) => {
|
|
||||||
const newProjectId = await newProject(knex, oldProjectId, ProjectType.CertificateManager);
|
|
||||||
await knex(TableName.CertificateAuthority).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
await knex(TableName.Certificate).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
await knex(TableName.PkiSubscriber).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
await knex(TableName.PkiCollection).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
await knex(TableName.PkiAlert).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const kickOutSecretScanningProject = async (knex: Knex, oldProjectId: string) => {
|
|
||||||
const newProjectId = await newProject(knex, oldProjectId, ProjectType.SecretScanning);
|
|
||||||
await knex(TableName.SecretScanningConfig).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
await knex(TableName.SecretScanningDataSource).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
await knex(TableName.SecretScanningFinding).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const kickOutKmsProject = async (knex: Knex, oldProjectId: string) => {
|
|
||||||
const newProjectId = await newProject(knex, oldProjectId, ProjectType.KMS);
|
|
||||||
await knex(TableName.KmsKey)
|
|
||||||
.where("projectId", oldProjectId)
|
|
||||||
.andWhere("isReserved", false)
|
|
||||||
.update("projectId", newProjectId);
|
|
||||||
await knex(TableName.KmipClient).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const kickOutSshProject = async (knex: Knex, oldProjectId: string) => {
|
|
||||||
const newProjectId = await newProject(knex, oldProjectId, ProjectType.SSH);
|
|
||||||
await knex(TableName.SshHost).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
await knex(TableName.ProjectSshConfig).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
await knex(TableName.SshCertificateAuthority).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
await knex(TableName.SshHostGroup).where("projectId", oldProjectId).update("projectId", newProjectId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const BATCH_SIZE = 1000;
|
|
||||||
const MIGRATION_TIMEOUT = 30 * 60 * 1000; // 30 minutes
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
const result = await knex.raw("SHOW statement_timeout");
|
|
||||||
const originalTimeout = result.rows[0].statement_timeout;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await knex.raw(`SET statement_timeout = ${MIGRATION_TIMEOUT}`);
|
|
||||||
|
|
||||||
const hasTemplateTypeColumn = await knex.schema.hasColumn(TableName.ProjectTemplates, "type");
|
|
||||||
if (hasTemplateTypeColumn) {
|
|
||||||
await knex(TableName.ProjectTemplates).whereNull("type").update({
|
|
||||||
type: ProjectType.SecretManager
|
|
||||||
});
|
|
||||||
await knex.schema.alterTable(TableName.ProjectTemplates, (t) => {
|
|
||||||
t.string("type").notNullable().defaultTo(ProjectType.SecretManager).alter();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasTypeColumn = await knex.schema.hasColumn(TableName.Project, "type");
|
|
||||||
const hasDefaultTypeColumn = await knex.schema.hasColumn(TableName.Project, "defaultProduct");
|
|
||||||
if (hasTypeColumn && hasDefaultTypeColumn) {
|
|
||||||
await knex(TableName.Project).update({
|
|
||||||
// eslint-disable-next-line
|
|
||||||
// @ts-ignore this is because this field is created later
|
|
||||||
type: knex.raw(`"defaultProduct"`)
|
|
||||||
});
|
|
||||||
|
|
||||||
await knex.schema.alterTable(TableName.Project, (t) => {
|
|
||||||
t.string("type").notNullable().alter();
|
|
||||||
t.string("defaultProduct").nullable().alter();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get all projects that need kickouts in a single query
|
|
||||||
const projectsNeedingKickouts = await getProjectsNeedingKickouts(knex);
|
|
||||||
|
|
||||||
// Process projects in batches to avoid overwhelming the database
|
|
||||||
for (let i = 0; i < projectsNeedingKickouts.length; i += projectsNeedingKickouts.length) {
|
|
||||||
const batch = projectsNeedingKickouts.slice(i, i + BATCH_SIZE);
|
|
||||||
const processedIds: string[] = [];
|
|
||||||
|
|
||||||
for (const project of batch) {
|
|
||||||
const kickoutPromises: Promise<void>[] = [];
|
|
||||||
|
|
||||||
// Only add kickouts that are actually needed (flags are pre-computed)
|
|
||||||
if (project.needsSecretManager) {
|
|
||||||
kickoutPromises.push(kickOutSecretManagerProject(knex, project.id));
|
|
||||||
}
|
|
||||||
if (project.needsCertManager) {
|
|
||||||
kickoutPromises.push(kickOutCertManagerProject(knex, project.id));
|
|
||||||
}
|
|
||||||
if (project.needsKms) {
|
|
||||||
kickoutPromises.push(kickOutKmsProject(knex, project.id));
|
|
||||||
}
|
|
||||||
if (project.needsSsh) {
|
|
||||||
kickoutPromises.push(kickOutSshProject(knex, project.id));
|
|
||||||
}
|
|
||||||
if (project.needsSecretScanning) {
|
|
||||||
kickoutPromises.push(kickOutSecretScanningProject(knex, project.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute all kickouts in parallel and handle any failures gracefully
|
|
||||||
if (kickoutPromises.length > 0) {
|
|
||||||
const results = await Promise.allSettled(kickoutPromises);
|
|
||||||
|
|
||||||
// Log any failures for debugging
|
|
||||||
results.forEach((res) => {
|
|
||||||
if (res.status === "rejected") {
|
|
||||||
throw new Error(`Migration failed for project ${project.id}: ${res.reason}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
processedIds.push(project.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear defaultProduct for the processed batch
|
|
||||||
if (processedIds.length > 0) {
|
|
||||||
await knex(TableName.Project).whereIn("id", processedIds).update("defaultProduct", null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
await knex.raw(`SET statement_timeout = '${originalTimeout}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
const hasTypeColumn = await knex.schema.hasColumn(TableName.Project, "type");
|
|
||||||
const hasDefaultTypeColumn = await knex.schema.hasColumn(TableName.Project, "defaultProduct");
|
|
||||||
if (hasTypeColumn && hasDefaultTypeColumn) {
|
|
||||||
await knex(TableName.Project).update({
|
|
||||||
// eslint-disable-next-line
|
|
||||||
// @ts-ignore this is because this field is created later
|
|
||||||
defaultProduct: knex.raw(`
|
|
||||||
CASE
|
|
||||||
WHEN "type" IS NULL OR "type" = '' THEN 'secret-manager'
|
|
||||||
ELSE "type"
|
|
||||||
END
|
|
||||||
`)
|
|
||||||
});
|
|
||||||
|
|
||||||
await knex.schema.alterTable(TableName.Project, (t) => {
|
|
||||||
t.string("type").nullable().alter();
|
|
||||||
t.string("defaultProduct").notNullable().alter();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasTemplateTypeColumn = await knex.schema.hasColumn(TableName.ProjectTemplates, "type");
|
|
||||||
if (hasTemplateTypeColumn) {
|
|
||||||
await knex.schema.alterTable(TableName.ProjectTemplates, (t) => {
|
|
||||||
t.string("type").nullable().alter();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,21 +0,0 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import { TableName } from "../schemas";
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
const hasColumn = await knex.schema.hasColumn(TableName.IdentityAwsAuth, "allowedPrincipalArns");
|
|
||||||
if (hasColumn) {
|
|
||||||
await knex.schema.alterTable(TableName.IdentityAwsAuth, (t) => {
|
|
||||||
t.string("allowedPrincipalArns", 4096).notNullable().alter();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
const hasColumn = await knex.schema.hasColumn(TableName.IdentityAwsAuth, "allowedPrincipalArns");
|
|
||||||
if (hasColumn) {
|
|
||||||
await knex.schema.alterTable(TableName.IdentityAwsAuth, (t) => {
|
|
||||||
t.string("allowedPrincipalArns", 2048).notNullable().alter();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,96 +0,0 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import { selectAllTableCols } from "@app/lib/knex";
|
|
||||||
|
|
||||||
import { TableName } from "../schemas";
|
|
||||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
if (!(await knex.schema.hasTable(TableName.AccessApprovalPolicyEnvironment))) {
|
|
||||||
await knex.schema.createTable(TableName.AccessApprovalPolicyEnvironment, (t) => {
|
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
|
||||||
t.uuid("policyId").notNullable();
|
|
||||||
t.foreign("policyId").references("id").inTable(TableName.AccessApprovalPolicy).onDelete("CASCADE");
|
|
||||||
t.uuid("envId").notNullable();
|
|
||||||
t.foreign("envId").references("id").inTable(TableName.Environment);
|
|
||||||
t.timestamps(true, true, true);
|
|
||||||
t.unique(["policyId", "envId"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
await createOnUpdateTrigger(knex, TableName.AccessApprovalPolicyEnvironment);
|
|
||||||
|
|
||||||
const existingAccessApprovalPolicies = await knex(TableName.AccessApprovalPolicy)
|
|
||||||
.select(selectAllTableCols(TableName.AccessApprovalPolicy))
|
|
||||||
.whereNotNull(`${TableName.AccessApprovalPolicy}.envId`);
|
|
||||||
|
|
||||||
const accessApprovalPolicies = existingAccessApprovalPolicies.map(async (policy) => {
|
|
||||||
await knex(TableName.AccessApprovalPolicyEnvironment).insert({
|
|
||||||
policyId: policy.id,
|
|
||||||
envId: policy.envId
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(accessApprovalPolicies);
|
|
||||||
}
|
|
||||||
if (!(await knex.schema.hasTable(TableName.SecretApprovalPolicyEnvironment))) {
|
|
||||||
await knex.schema.createTable(TableName.SecretApprovalPolicyEnvironment, (t) => {
|
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
|
||||||
t.uuid("policyId").notNullable();
|
|
||||||
t.foreign("policyId").references("id").inTable(TableName.SecretApprovalPolicy).onDelete("CASCADE");
|
|
||||||
t.uuid("envId").notNullable();
|
|
||||||
t.foreign("envId").references("id").inTable(TableName.Environment);
|
|
||||||
t.timestamps(true, true, true);
|
|
||||||
t.unique(["policyId", "envId"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
await createOnUpdateTrigger(knex, TableName.SecretApprovalPolicyEnvironment);
|
|
||||||
|
|
||||||
const existingSecretApprovalPolicies = await knex(TableName.SecretApprovalPolicy)
|
|
||||||
.select(selectAllTableCols(TableName.SecretApprovalPolicy))
|
|
||||||
.whereNotNull(`${TableName.SecretApprovalPolicy}.envId`);
|
|
||||||
|
|
||||||
const secretApprovalPolicies = existingSecretApprovalPolicies.map(async (policy) => {
|
|
||||||
await knex(TableName.SecretApprovalPolicyEnvironment).insert({
|
|
||||||
policyId: policy.id,
|
|
||||||
envId: policy.envId
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(secretApprovalPolicies);
|
|
||||||
}
|
|
||||||
|
|
||||||
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (t) => {
|
|
||||||
t.dropForeign(["envId"]);
|
|
||||||
|
|
||||||
// Add the new foreign key constraint with ON DELETE SET NULL
|
|
||||||
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("SET NULL");
|
|
||||||
});
|
|
||||||
|
|
||||||
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (t) => {
|
|
||||||
t.dropForeign(["envId"]);
|
|
||||||
|
|
||||||
// Add the new foreign key constraint with ON DELETE SET NULL
|
|
||||||
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("SET NULL");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
if (await knex.schema.hasTable(TableName.AccessApprovalPolicyEnvironment)) {
|
|
||||||
await knex.schema.dropTableIfExists(TableName.AccessApprovalPolicyEnvironment);
|
|
||||||
await dropOnUpdateTrigger(knex, TableName.AccessApprovalPolicyEnvironment);
|
|
||||||
}
|
|
||||||
if (await knex.schema.hasTable(TableName.SecretApprovalPolicyEnvironment)) {
|
|
||||||
await knex.schema.dropTableIfExists(TableName.SecretApprovalPolicyEnvironment);
|
|
||||||
await dropOnUpdateTrigger(knex, TableName.SecretApprovalPolicyEnvironment);
|
|
||||||
}
|
|
||||||
|
|
||||||
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (t) => {
|
|
||||||
t.dropForeign(["envId"]);
|
|
||||||
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
|
|
||||||
});
|
|
||||||
|
|
||||||
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (t) => {
|
|
||||||
t.dropForeign(["envId"]);
|
|
||||||
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,111 +0,0 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
|
||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import { chunkArray } from "@app/lib/fn";
|
|
||||||
import { logger } from "@app/lib/logger";
|
|
||||||
|
|
||||||
import { TableName } from "../schemas";
|
|
||||||
import { TReminders, TRemindersInsert } from "../schemas/reminders";
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
logger.info("Initializing secret reminders migration");
|
|
||||||
const hasReminderTable = await knex.schema.hasTable(TableName.Reminder);
|
|
||||||
|
|
||||||
if (hasReminderTable) {
|
|
||||||
const secretsWithLatestVersions = await knex(TableName.SecretV2)
|
|
||||||
.whereNotNull(`${TableName.SecretV2}.reminderRepeatDays`)
|
|
||||||
.whereRaw(`"${TableName.SecretV2}"."reminderRepeatDays" > 0`)
|
|
||||||
.innerJoin(TableName.SecretVersionV2, (qb) => {
|
|
||||||
void qb
|
|
||||||
.on(`${TableName.SecretVersionV2}.secretId`, "=", `${TableName.SecretV2}.id`)
|
|
||||||
.andOn(`${TableName.SecretVersionV2}.reminderRepeatDays`, "=", `${TableName.SecretV2}.reminderRepeatDays`);
|
|
||||||
})
|
|
||||||
.whereIn([`${TableName.SecretVersionV2}.secretId`, `${TableName.SecretVersionV2}.version`], (qb) => {
|
|
||||||
void qb
|
|
||||||
.select(["v2.secretId", knex.raw("MIN(v2.version) as version")])
|
|
||||||
.from(`${TableName.SecretVersionV2} as v2`)
|
|
||||||
.innerJoin(`${TableName.SecretV2} as s2`, "v2.secretId", "s2.id")
|
|
||||||
.whereRaw(`v2."reminderRepeatDays" = s2."reminderRepeatDays"`)
|
|
||||||
.whereNotNull("v2.reminderRepeatDays")
|
|
||||||
.whereRaw(`v2."reminderRepeatDays" > 0`)
|
|
||||||
.groupBy("v2.secretId");
|
|
||||||
})
|
|
||||||
// Add LEFT JOIN with Reminder table to check for existing reminders
|
|
||||||
.leftJoin(TableName.Reminder, `${TableName.Reminder}.secretId`, `${TableName.SecretV2}.id`)
|
|
||||||
// Only include secrets that don't already have reminders
|
|
||||||
.whereNull(`${TableName.Reminder}.secretId`)
|
|
||||||
.select(
|
|
||||||
knex.ref("id").withSchema(TableName.SecretV2).as("secretId"),
|
|
||||||
knex.ref("reminderRepeatDays").withSchema(TableName.SecretV2).as("reminderRepeatDays"),
|
|
||||||
knex.ref("reminderNote").withSchema(TableName.SecretV2).as("reminderNote"),
|
|
||||||
knex.ref("createdAt").withSchema(TableName.SecretVersionV2).as("createdAt")
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.info(`Found ${secretsWithLatestVersions.length} reminders to migrate`);
|
|
||||||
|
|
||||||
const reminderInserts: TRemindersInsert[] = [];
|
|
||||||
if (secretsWithLatestVersions.length > 0) {
|
|
||||||
secretsWithLatestVersions.forEach((secret) => {
|
|
||||||
if (!secret.reminderRepeatDays) return;
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const createdAt = new Date(secret.createdAt);
|
|
||||||
let nextReminderDate = new Date(createdAt);
|
|
||||||
nextReminderDate.setDate(nextReminderDate.getDate() + secret.reminderRepeatDays);
|
|
||||||
|
|
||||||
// If the next reminder date is in the past, calculate the proper next occurrence
|
|
||||||
if (nextReminderDate < now) {
|
|
||||||
const daysSinceCreation = Math.floor((now.getTime() - createdAt.getTime()) / (1000 * 60 * 60 * 24));
|
|
||||||
const daysIntoCurrentCycle = daysSinceCreation % secret.reminderRepeatDays;
|
|
||||||
const daysUntilNextReminder = secret.reminderRepeatDays - daysIntoCurrentCycle;
|
|
||||||
|
|
||||||
nextReminderDate = new Date(now);
|
|
||||||
nextReminderDate.setDate(now.getDate() + daysUntilNextReminder);
|
|
||||||
}
|
|
||||||
|
|
||||||
reminderInserts.push({
|
|
||||||
secretId: secret.secretId,
|
|
||||||
message: secret.reminderNote,
|
|
||||||
repeatDays: secret.reminderRepeatDays,
|
|
||||||
nextReminderDate
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const commitBatches = chunkArray(reminderInserts, 2000);
|
|
||||||
for (const commitBatch of commitBatches) {
|
|
||||||
const insertedReminders = (await knex
|
|
||||||
.batchInsert(TableName.Reminder, commitBatch)
|
|
||||||
.returning("*")) as TReminders[];
|
|
||||||
|
|
||||||
const insertedReminderSecretIds = insertedReminders.map((reminder) => reminder.secretId).filter(Boolean);
|
|
||||||
|
|
||||||
const recipients = await knex(TableName.SecretReminderRecipients)
|
|
||||||
.whereRaw(`??.?? IN (${insertedReminderSecretIds.map(() => "?").join(",")})`, [
|
|
||||||
TableName.SecretReminderRecipients,
|
|
||||||
"secretId",
|
|
||||||
...insertedReminderSecretIds
|
|
||||||
])
|
|
||||||
.select(
|
|
||||||
knex.ref("userId").withSchema(TableName.SecretReminderRecipients).as("userId"),
|
|
||||||
knex.ref("secretId").withSchema(TableName.SecretReminderRecipients).as("secretId")
|
|
||||||
);
|
|
||||||
const reminderRecipients = recipients.map((recipient) => ({
|
|
||||||
reminderId: insertedReminders.find((reminder) => reminder.secretId === recipient.secretId)?.id,
|
|
||||||
userId: recipient.userId
|
|
||||||
}));
|
|
||||||
|
|
||||||
const filteredRecipients = reminderRecipients.filter((recipient) => Boolean(recipient.reminderId));
|
|
||||||
await knex.batchInsert(TableName.ReminderRecipient, filteredRecipients);
|
|
||||||
}
|
|
||||||
logger.info(`Successfully migrated ${reminderInserts.length} secret reminders`);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("Secret reminders migration completed");
|
|
||||||
} else {
|
|
||||||
logger.warn("Reminder table does not exist, skipping migration");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(): Promise<void> {
|
|
||||||
logger.info("Rollback not implemented for secret reminders fix migration");
|
|
||||||
}
|
|
@@ -1,19 +0,0 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import { TableName } from "../schemas";
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
if (!(await knex.schema.hasColumn(TableName.Project, "secretDetectionIgnoreValues"))) {
|
|
||||||
await knex.schema.alterTable(TableName.Project, (t) => {
|
|
||||||
t.specificType("secretDetectionIgnoreValues", "text[]");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
if (await knex.schema.hasColumn(TableName.Project, "secretDetectionIgnoreValues")) {
|
|
||||||
await knex.schema.alterTable(TableName.Project, (t) => {
|
|
||||||
t.dropColumn("secretDetectionIgnoreValues");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@@ -53,7 +53,7 @@ export const getMigrationEnvConfig = async (superAdminDAL: TSuperAdminDALFactory
|
|||||||
|
|
||||||
let envCfg = Object.freeze(parsedEnv.data);
|
let envCfg = Object.freeze(parsedEnv.data);
|
||||||
|
|
||||||
const fipsEnabled = await crypto.initialize(superAdminDAL, envCfg);
|
const fipsEnabled = await crypto.initialize(superAdminDAL);
|
||||||
|
|
||||||
// Fix for 128-bit entropy encryption key expansion issue:
|
// Fix for 128-bit entropy encryption key expansion issue:
|
||||||
// In FIPS it is not ideal to expand a 128-bit key into 256-bit. We solved this issue in the past by creating the ROOT_ENCRYPTION_KEY.
|
// In FIPS it is not ideal to expand a 128-bit key into 256-bit. We solved this issue in the past by creating the ROOT_ENCRYPTION_KEY.
|
||||||
|
@@ -1,25 +0,0 @@
|
|||||||
// Code generated by automation script, DO NOT EDIT.
|
|
||||||
// Automated by pulling database and generating zod schema
|
|
||||||
// To update. Just run npm run generate:schema
|
|
||||||
// Written by akhilmhdh.
|
|
||||||
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
|
||||||
|
|
||||||
export const AccessApprovalPoliciesEnvironmentsSchema = z.object({
|
|
||||||
id: z.string().uuid(),
|
|
||||||
policyId: z.string().uuid(),
|
|
||||||
envId: z.string().uuid(),
|
|
||||||
createdAt: z.date(),
|
|
||||||
updatedAt: z.date()
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TAccessApprovalPoliciesEnvironments = z.infer<typeof AccessApprovalPoliciesEnvironmentsSchema>;
|
|
||||||
export type TAccessApprovalPoliciesEnvironmentsInsert = Omit<
|
|
||||||
z.input<typeof AccessApprovalPoliciesEnvironmentsSchema>,
|
|
||||||
TImmutableDBKeys
|
|
||||||
>;
|
|
||||||
export type TAccessApprovalPoliciesEnvironmentsUpdate = Partial<
|
|
||||||
Omit<z.input<typeof AccessApprovalPoliciesEnvironmentsSchema>, TImmutableDBKeys>
|
|
||||||
>;
|
|
@@ -20,8 +20,7 @@ export const AppConnectionsSchema = z.object({
|
|||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
isPlatformManagedCredentials: z.boolean().default(false).nullable().optional(),
|
isPlatformManagedCredentials: z.boolean().default(false).nullable().optional()
|
||||||
gatewayId: z.string().uuid().nullable().optional()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TAppConnections = z.infer<typeof AppConnectionsSchema>;
|
export type TAppConnections = z.infer<typeof AppConnectionsSchema>;
|
||||||
|
@@ -100,7 +100,6 @@ export enum TableName {
|
|||||||
AccessApprovalPolicyBypasser = "access_approval_policies_bypassers",
|
AccessApprovalPolicyBypasser = "access_approval_policies_bypassers",
|
||||||
AccessApprovalRequest = "access_approval_requests",
|
AccessApprovalRequest = "access_approval_requests",
|
||||||
AccessApprovalRequestReviewer = "access_approval_requests_reviewers",
|
AccessApprovalRequestReviewer = "access_approval_requests_reviewers",
|
||||||
AccessApprovalPolicyEnvironment = "access_approval_policies_environments",
|
|
||||||
SecretApprovalPolicy = "secret_approval_policies",
|
SecretApprovalPolicy = "secret_approval_policies",
|
||||||
SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
|
SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
|
||||||
SecretApprovalPolicyBypasser = "secret_approval_policies_bypassers",
|
SecretApprovalPolicyBypasser = "secret_approval_policies_bypassers",
|
||||||
@@ -108,7 +107,6 @@ export enum TableName {
|
|||||||
SecretApprovalRequestReviewer = "secret_approval_requests_reviewers",
|
SecretApprovalRequestReviewer = "secret_approval_requests_reviewers",
|
||||||
SecretApprovalRequestSecret = "secret_approval_requests_secrets",
|
SecretApprovalRequestSecret = "secret_approval_requests_secrets",
|
||||||
SecretApprovalRequestSecretTag = "secret_approval_request_secret_tags",
|
SecretApprovalRequestSecretTag = "secret_approval_request_secret_tags",
|
||||||
SecretApprovalPolicyEnvironment = "secret_approval_policies_environments",
|
|
||||||
SecretRotation = "secret_rotations",
|
SecretRotation = "secret_rotations",
|
||||||
SecretRotationOutput = "secret_rotation_outputs",
|
SecretRotationOutput = "secret_rotation_outputs",
|
||||||
SamlConfig = "saml_configs",
|
SamlConfig = "saml_configs",
|
||||||
@@ -162,7 +160,7 @@ export enum TableName {
|
|||||||
SecretRotationV2SecretMapping = "secret_rotation_v2_secret_mappings",
|
SecretRotationV2SecretMapping = "secret_rotation_v2_secret_mappings",
|
||||||
MicrosoftTeamsIntegrations = "microsoft_teams_integrations",
|
MicrosoftTeamsIntegrations = "microsoft_teams_integrations",
|
||||||
ProjectMicrosoftTeamsConfigs = "project_microsoft_teams_configs",
|
ProjectMicrosoftTeamsConfigs = "project_microsoft_teams_configs",
|
||||||
SecretReminderRecipients = "secret_reminder_recipients", // TODO(Carlos): Remove this in the future after migrating to the new reminder recipients table
|
SecretReminderRecipients = "secret_reminder_recipients",
|
||||||
GithubOrgSyncConfig = "github_org_sync_configs",
|
GithubOrgSyncConfig = "github_org_sync_configs",
|
||||||
FolderCommit = "folder_commits",
|
FolderCommit = "folder_commits",
|
||||||
FolderCommitChanges = "folder_commit_changes",
|
FolderCommitChanges = "folder_commit_changes",
|
||||||
@@ -174,10 +172,7 @@ export enum TableName {
|
|||||||
SecretScanningResource = "secret_scanning_resources",
|
SecretScanningResource = "secret_scanning_resources",
|
||||||
SecretScanningScan = "secret_scanning_scans",
|
SecretScanningScan = "secret_scanning_scans",
|
||||||
SecretScanningFinding = "secret_scanning_findings",
|
SecretScanningFinding = "secret_scanning_findings",
|
||||||
SecretScanningConfig = "secret_scanning_configs",
|
SecretScanningConfig = "secret_scanning_configs"
|
||||||
// reminders
|
|
||||||
Reminder = "reminders",
|
|
||||||
ReminderRecipient = "reminders_recipients"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt" | "commitId";
|
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt" | "commitId";
|
||||||
@@ -272,16 +267,6 @@ export enum ProjectType {
|
|||||||
SecretScanning = "secret-scanning"
|
SecretScanning = "secret-scanning"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ActionProjectType {
|
|
||||||
SecretManager = ProjectType.SecretManager,
|
|
||||||
CertificateManager = ProjectType.CertificateManager,
|
|
||||||
KMS = ProjectType.KMS,
|
|
||||||
SSH = ProjectType.SSH,
|
|
||||||
SecretScanning = ProjectType.SecretScanning,
|
|
||||||
// project operations that happen on all types
|
|
||||||
Any = "any"
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum SortDirection {
|
export enum SortDirection {
|
||||||
ASC = "asc",
|
ASC = "asc",
|
||||||
DESC = "desc"
|
DESC = "desc"
|
||||||
|
@@ -16,7 +16,7 @@ export const ProjectTemplatesSchema = z.object({
|
|||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
type: z.string().default("secret-manager")
|
type: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TProjectTemplates = z.infer<typeof ProjectTemplatesSchema>;
|
export type TProjectTemplates = z.infer<typeof ProjectTemplatesSchema>;
|
||||||
|
@@ -25,13 +25,12 @@ export const ProjectsSchema = z.object({
|
|||||||
kmsSecretManagerKeyId: z.string().uuid().nullable().optional(),
|
kmsSecretManagerKeyId: z.string().uuid().nullable().optional(),
|
||||||
kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional(),
|
kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional(),
|
||||||
description: z.string().nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
type: z.string(),
|
type: z.string().nullable().optional(),
|
||||||
enforceCapitalization: z.boolean().default(false),
|
enforceCapitalization: z.boolean().default(false),
|
||||||
hasDeleteProtection: z.boolean().default(false).nullable().optional(),
|
hasDeleteProtection: z.boolean().default(false).nullable().optional(),
|
||||||
secretSharing: z.boolean().default(true),
|
secretSharing: z.boolean().default(true),
|
||||||
showSnapshotsLegacy: z.boolean().default(false),
|
showSnapshotsLegacy: z.boolean().default(false),
|
||||||
defaultProduct: z.string().nullable().optional(),
|
defaultProduct: z.string().default("secret-manager")
|
||||||
secretDetectionIgnoreValues: z.string().array().nullable().optional()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TProjects = z.infer<typeof ProjectsSchema>;
|
export type TProjects = z.infer<typeof ProjectsSchema>;
|
||||||
|
@@ -1,20 +0,0 @@
|
|||||||
// Code generated by automation script, DO NOT EDIT.
|
|
||||||
// Automated by pulling database and generating zod schema
|
|
||||||
// To update. Just run npm run generate:schema
|
|
||||||
// Written by akhilmhdh.
|
|
||||||
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
|
||||||
|
|
||||||
export const RemindersRecipientsSchema = z.object({
|
|
||||||
id: z.string().uuid(),
|
|
||||||
reminderId: z.string().uuid(),
|
|
||||||
userId: z.string().uuid(),
|
|
||||||
createdAt: z.date(),
|
|
||||||
updatedAt: z.date()
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TRemindersRecipients = z.infer<typeof RemindersRecipientsSchema>;
|
|
||||||
export type TRemindersRecipientsInsert = Omit<z.input<typeof RemindersRecipientsSchema>, TImmutableDBKeys>;
|
|
||||||
export type TRemindersRecipientsUpdate = Partial<Omit<z.input<typeof RemindersRecipientsSchema>, TImmutableDBKeys>>;
|
|
@@ -1,22 +0,0 @@
|
|||||||
// Code generated by automation script, DO NOT EDIT.
|
|
||||||
// Automated by pulling database and generating zod schema
|
|
||||||
// To update. Just run npm run generate:schema
|
|
||||||
// Written by akhilmhdh.
|
|
||||||
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
|
||||||
|
|
||||||
export const RemindersSchema = z.object({
|
|
||||||
id: z.string().uuid(),
|
|
||||||
secretId: z.string().uuid().nullable().optional(),
|
|
||||||
message: z.string().nullable().optional(),
|
|
||||||
repeatDays: z.number().nullable().optional(),
|
|
||||||
nextReminderDate: z.date(),
|
|
||||||
createdAt: z.date(),
|
|
||||||
updatedAt: z.date()
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TReminders = z.infer<typeof RemindersSchema>;
|
|
||||||
export type TRemindersInsert = Omit<z.input<typeof RemindersSchema>, TImmutableDBKeys>;
|
|
||||||
export type TRemindersUpdate = Partial<Omit<z.input<typeof RemindersSchema>, TImmutableDBKeys>>;
|
|
@@ -1,25 +0,0 @@
|
|||||||
// Code generated by automation script, DO NOT EDIT.
|
|
||||||
// Automated by pulling database and generating zod schema
|
|
||||||
// To update. Just run npm run generate:schema
|
|
||||||
// Written by akhilmhdh.
|
|
||||||
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
|
||||||
|
|
||||||
export const SecretApprovalPoliciesEnvironmentsSchema = z.object({
|
|
||||||
id: z.string().uuid(),
|
|
||||||
policyId: z.string().uuid(),
|
|
||||||
envId: z.string().uuid(),
|
|
||||||
createdAt: z.date(),
|
|
||||||
updatedAt: z.date()
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TSecretApprovalPoliciesEnvironments = z.infer<typeof SecretApprovalPoliciesEnvironmentsSchema>;
|
|
||||||
export type TSecretApprovalPoliciesEnvironmentsInsert = Omit<
|
|
||||||
z.input<typeof SecretApprovalPoliciesEnvironmentsSchema>,
|
|
||||||
TImmutableDBKeys
|
|
||||||
>;
|
|
||||||
export type TSecretApprovalPoliciesEnvironmentsUpdate = Partial<
|
|
||||||
Omit<z.input<typeof SecretApprovalPoliciesEnvironmentsSchema>, TImmutableDBKeys>
|
|
||||||
>;
|
|
@@ -17,66 +17,52 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z
|
body: z.object({
|
||||||
.object({
|
projectSlug: z.string().trim(),
|
||||||
projectSlug: z.string().trim(),
|
name: z.string().optional(),
|
||||||
name: z.string().optional(),
|
secretPath: z.string().trim().min(1, { message: "Secret path cannot be empty" }).transform(removeTrailingSlash),
|
||||||
secretPath: z
|
environment: z.string(),
|
||||||
.string()
|
approvers: z
|
||||||
.trim()
|
.discriminatedUnion("type", [
|
||||||
.min(1, { message: "Secret path cannot be empty" })
|
z.object({
|
||||||
.transform(removeTrailingSlash),
|
type: z.literal(ApproverType.Group),
|
||||||
environment: z.string().optional(),
|
id: z.string(),
|
||||||
environments: z.string().array().optional(),
|
sequence: z.number().int().default(1)
|
||||||
approvers: z
|
}),
|
||||||
.discriminatedUnion("type", [
|
z.object({
|
||||||
z.object({
|
type: z.literal(ApproverType.User),
|
||||||
type: z.literal(ApproverType.Group),
|
id: z.string().optional(),
|
||||||
id: z.string(),
|
username: z.string().optional(),
|
||||||
sequence: z.number().int().default(1)
|
sequence: z.number().int().default(1)
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
type: z.literal(ApproverType.User),
|
|
||||||
id: z.string().optional(),
|
|
||||||
username: z.string().optional(),
|
|
||||||
sequence: z.number().int().default(1)
|
|
||||||
})
|
|
||||||
])
|
|
||||||
.array()
|
|
||||||
.max(100, "Cannot have more than 100 approvers")
|
|
||||||
.min(1, { message: "At least one approver should be provided" })
|
|
||||||
.refine(
|
|
||||||
// @ts-expect-error this is ok
|
|
||||||
(el) => el.every((i) => Boolean(i?.id) || Boolean(i?.username)),
|
|
||||||
"Must provide either username or id"
|
|
||||||
),
|
|
||||||
bypassers: z
|
|
||||||
.discriminatedUnion("type", [
|
|
||||||
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
|
|
||||||
z.object({
|
|
||||||
type: z.literal(BypasserType.User),
|
|
||||||
id: z.string().optional(),
|
|
||||||
username: z.string().optional()
|
|
||||||
})
|
|
||||||
])
|
|
||||||
.array()
|
|
||||||
.max(100, "Cannot have more than 100 bypassers")
|
|
||||||
.optional(),
|
|
||||||
approvalsRequired: z
|
|
||||||
.object({
|
|
||||||
numberOfApprovals: z.number().int(),
|
|
||||||
stepNumber: z.number().int()
|
|
||||||
})
|
})
|
||||||
.array()
|
])
|
||||||
.optional(),
|
.array()
|
||||||
approvals: z.number().min(1).default(1),
|
.max(100, "Cannot have more than 100 approvers")
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
.min(1, { message: "At least one approver should be provided" })
|
||||||
allowedSelfApprovals: z.boolean().default(true)
|
.refine(
|
||||||
})
|
// @ts-expect-error this is ok
|
||||||
.refine(
|
(el) => el.every((i) => Boolean(i?.id) || Boolean(i?.username)),
|
||||||
(val) => Boolean(val.environment) || Boolean(val.environments),
|
"Must provide either username or id"
|
||||||
"Must provide either environment or environments"
|
),
|
||||||
),
|
bypassers: z
|
||||||
|
.discriminatedUnion("type", [
|
||||||
|
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
|
||||||
|
z.object({ type: z.literal(BypasserType.User), id: z.string().optional(), username: z.string().optional() })
|
||||||
|
])
|
||||||
|
.array()
|
||||||
|
.max(100, "Cannot have more than 100 bypassers")
|
||||||
|
.optional(),
|
||||||
|
approvalsRequired: z
|
||||||
|
.object({
|
||||||
|
numberOfApprovals: z.number().int(),
|
||||||
|
stepNumber: z.number().int()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.optional(),
|
||||||
|
approvals: z.number().min(1).default(1),
|
||||||
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
||||||
|
allowedSelfApprovals: z.boolean().default(true)
|
||||||
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
approval: sapPubSchema
|
approval: sapPubSchema
|
||||||
@@ -92,8 +78,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body,
|
...req.body,
|
||||||
projectSlug: req.body.projectSlug,
|
projectSlug: req.body.projectSlug,
|
||||||
name:
|
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`,
|
||||||
req.body.name ?? `${req.body.environment || req.body.environments?.join("-").substring(0, 250)}-${nanoid(3)}`,
|
|
||||||
enforcementLevel: req.body.enforcementLevel
|
enforcementLevel: req.body.enforcementLevel
|
||||||
});
|
});
|
||||||
return { approval };
|
return { approval };
|
||||||
@@ -226,7 +211,6 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
approvals: z.number().min(1).optional(),
|
approvals: z.number().min(1).optional(),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
||||||
allowedSelfApprovals: z.boolean().default(true),
|
allowedSelfApprovals: z.boolean().default(true),
|
||||||
environments: z.array(z.string()).optional(),
|
|
||||||
approvalsRequired: z
|
approvalsRequired: z
|
||||||
.object({
|
.object({
|
||||||
numberOfApprovals: z.number().int(),
|
numberOfApprovals: z.number().int(),
|
||||||
|
@@ -3,14 +3,11 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { isValidFolderName } from "@app/lib/validator";
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
import { readLimit, secretsLimit } from "@app/server/config/rateLimiter";
|
|
||||||
import { SecretNameSchema } from "@app/server/lib/schemas";
|
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { booleanSchema } from "@app/server/routes/sanitizedSchemas";
|
import { booleanSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { commitChangesResponseSchema, resourceChangeSchema } from "@app/services/folder-commit/folder-commit-schemas";
|
import { commitChangesResponseSchema, resourceChangeSchema } from "@app/services/folder-commit/folder-commit-schemas";
|
||||||
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
|
|
||||||
|
|
||||||
const commitHistoryItemSchema = z.object({
|
const commitHistoryItemSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
@@ -416,166 +413,4 @@ export const registerPITRouter = async (server: FastifyZodProvider) => {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "POST",
|
|
||||||
url: "/batch/commit",
|
|
||||||
config: {
|
|
||||||
rateLimit: secretsLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
hide: true,
|
|
||||||
description: "Commit changes",
|
|
||||||
security: [
|
|
||||||
{
|
|
||||||
bearerAuth: []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
body: z.object({
|
|
||||||
projectId: z.string().trim(),
|
|
||||||
environment: z.string().trim(),
|
|
||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
|
||||||
message: z
|
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.min(1)
|
|
||||||
.max(255)
|
|
||||||
.refine((message) => message.trim() !== "", {
|
|
||||||
message: "Commit message cannot be empty"
|
|
||||||
}),
|
|
||||||
changes: z.object({
|
|
||||||
secrets: z.object({
|
|
||||||
create: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
secretKey: SecretNameSchema,
|
|
||||||
secretValue: z.string().transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())),
|
|
||||||
secretComment: z.string().trim().optional().default(""),
|
|
||||||
skipMultilineEncoding: z.boolean().optional(),
|
|
||||||
metadata: z.record(z.string()).optional(),
|
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
|
||||||
tagIds: z.string().array().optional()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
update: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
secretKey: SecretNameSchema,
|
|
||||||
newSecretName: SecretNameSchema.optional(),
|
|
||||||
secretValue: z
|
|
||||||
.string()
|
|
||||||
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
|
|
||||||
.optional(),
|
|
||||||
secretComment: z.string().trim().optional().default(""),
|
|
||||||
skipMultilineEncoding: z.boolean().optional(),
|
|
||||||
metadata: z.record(z.string()).optional(),
|
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
|
||||||
tagIds: z.string().array().optional()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
delete: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
secretKey: SecretNameSchema
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.optional()
|
|
||||||
}),
|
|
||||||
folders: z.object({
|
|
||||||
create: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
folderName: z
|
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.refine((name) => isValidFolderName(name), {
|
|
||||||
message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed."
|
|
||||||
}),
|
|
||||||
description: z.string().optional()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
update: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
folderName: z
|
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.refine((name) => isValidFolderName(name), {
|
|
||||||
message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed."
|
|
||||||
}),
|
|
||||||
description: z.string().nullable().optional(),
|
|
||||||
id: z.string()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
delete: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
folderName: z
|
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.refine((name) => isValidFolderName(name), {
|
|
||||||
message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed."
|
|
||||||
}),
|
|
||||||
id: z.string()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.optional()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
message: z.string()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const result = await server.services.pit.processNewCommitRaw({
|
|
||||||
actorId: req.permission.id,
|
|
||||||
actor: req.permission.type,
|
|
||||||
actorOrgId: req.permission.orgId,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
projectId: req.body.projectId,
|
|
||||||
environment: req.body.environment,
|
|
||||||
secretPath: req.body.secretPath,
|
|
||||||
message: req.body.message,
|
|
||||||
changes: {
|
|
||||||
secrets: req.body.changes.secrets,
|
|
||||||
folders: req.body.changes.folders
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
|
||||||
...req.auditLogInfo,
|
|
||||||
projectId: req.body.projectId,
|
|
||||||
event: {
|
|
||||||
type: EventType.PIT_PROCESS_NEW_COMMIT_RAW,
|
|
||||||
metadata: {
|
|
||||||
commitId: result.commitId,
|
|
||||||
approvalId: result.approvalId,
|
|
||||||
projectId: req.body.projectId,
|
|
||||||
environment: req.body.environment,
|
|
||||||
secretPath: req.body.secretPath,
|
|
||||||
message: req.body.message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for await (const event of result.secretMutationEvents) {
|
|
||||||
await server.services.auditLog.createAuditLog({
|
|
||||||
...req.auditLogInfo,
|
|
||||||
orgId: req.permission.orgId,
|
|
||||||
projectId: req.body.projectId,
|
|
||||||
event
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return { message: "success" };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectMembershipRole, ProjectTemplatesSchema, ProjectType } from "@app/db/schemas";
|
import { ProjectMembershipRole, ProjectTemplatesSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
||||||
import { isInfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
|
import { isInfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
|
||||||
@@ -104,9 +104,6 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
|
|||||||
hide: false,
|
hide: false,
|
||||||
tags: [ApiDocsTags.ProjectTemplates],
|
tags: [ApiDocsTags.ProjectTemplates],
|
||||||
description: "List project templates for the current organization.",
|
description: "List project templates for the current organization.",
|
||||||
querystring: z.object({
|
|
||||||
type: z.nativeEnum(ProjectType).optional().describe(ProjectTemplates.LIST.type)
|
|
||||||
}),
|
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
projectTemplates: SanitizedProjectTemplateSchema.array()
|
projectTemplates: SanitizedProjectTemplateSchema.array()
|
||||||
@@ -115,10 +112,7 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(
|
const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(req.permission);
|
||||||
req.permission,
|
|
||||||
req.query.type
|
|
||||||
);
|
|
||||||
|
|
||||||
const auditTemplates = projectTemplates.filter((template) => !isInfisicalProjectTemplate(template.name));
|
const auditTemplates = projectTemplates.filter((template) => !isInfisicalProjectTemplate(template.name));
|
||||||
|
|
||||||
@@ -197,7 +191,6 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
|
|||||||
.describe(ProjectTemplates.CREATE.name),
|
.describe(ProjectTemplates.CREATE.name),
|
||||||
description: z.string().max(256).trim().optional().describe(ProjectTemplates.CREATE.description),
|
description: z.string().max(256).trim().optional().describe(ProjectTemplates.CREATE.description),
|
||||||
roles: ProjectTemplateRolesSchema.default([]).describe(ProjectTemplates.CREATE.roles),
|
roles: ProjectTemplateRolesSchema.default([]).describe(ProjectTemplates.CREATE.roles),
|
||||||
type: z.nativeEnum(ProjectType).describe(ProjectTemplates.CREATE.type),
|
|
||||||
environments: ProjectTemplateEnvironmentsSchema.describe(ProjectTemplates.CREATE.environments).optional()
|
environments: ProjectTemplateEnvironmentsSchema.describe(ProjectTemplates.CREATE.environments).optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
@@ -17,45 +17,34 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z
|
body: z.object({
|
||||||
.object({
|
workspaceId: z.string(),
|
||||||
workspaceId: z.string(),
|
name: z.string().optional(),
|
||||||
name: z.string().optional(),
|
environment: z.string(),
|
||||||
environment: z.string().optional(),
|
secretPath: z
|
||||||
environments: z.string().array().optional(),
|
.string()
|
||||||
secretPath: z
|
.min(1, { message: "Secret path cannot be empty" })
|
||||||
.string()
|
.transform((val) => removeTrailingSlash(val)),
|
||||||
.min(1, { message: "Secret path cannot be empty" })
|
approvers: z
|
||||||
.transform((val) => removeTrailingSlash(val)),
|
.discriminatedUnion("type", [
|
||||||
approvers: z
|
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||||
.discriminatedUnion("type", [
|
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() })
|
||||||
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
])
|
||||||
z.object({
|
.array()
|
||||||
type: z.literal(ApproverType.User),
|
.min(1, { message: "At least one approver should be provided" })
|
||||||
id: z.string().optional(),
|
.max(100, "Cannot have more than 100 approvers"),
|
||||||
username: z.string().optional()
|
bypassers: z
|
||||||
})
|
.discriminatedUnion("type", [
|
||||||
])
|
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
|
||||||
.array()
|
z.object({ type: z.literal(BypasserType.User), id: z.string().optional(), username: z.string().optional() })
|
||||||
.min(1, { message: "At least one approver should be provided" })
|
])
|
||||||
.max(100, "Cannot have more than 100 approvers"),
|
.array()
|
||||||
bypassers: z
|
.max(100, "Cannot have more than 100 bypassers")
|
||||||
.discriminatedUnion("type", [
|
.optional(),
|
||||||
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
|
approvals: z.number().min(1).default(1),
|
||||||
z.object({
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
||||||
type: z.literal(BypasserType.User),
|
allowedSelfApprovals: z.boolean().default(true)
|
||||||
id: z.string().optional(),
|
}),
|
||||||
username: z.string().optional()
|
|
||||||
})
|
|
||||||
])
|
|
||||||
.array()
|
|
||||||
.max(100, "Cannot have more than 100 bypassers")
|
|
||||||
.optional(),
|
|
||||||
approvals: z.number().min(1).default(1),
|
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
|
||||||
allowedSelfApprovals: z.boolean().default(true)
|
|
||||||
})
|
|
||||||
.refine((data) => data.environment || data.environments, "At least one environment should be provided"),
|
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
approval: sapPubSchema
|
approval: sapPubSchema
|
||||||
@@ -71,7 +60,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId: req.body.workspaceId,
|
projectId: req.body.workspaceId,
|
||||||
...req.body,
|
...req.body,
|
||||||
name: req.body.name ?? `${req.body.environment || req.body.environments?.join(",")}-${nanoid(3)}`,
|
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`,
|
||||||
enforcementLevel: req.body.enforcementLevel
|
enforcementLevel: req.body.enforcementLevel
|
||||||
});
|
});
|
||||||
return { approval };
|
return { approval };
|
||||||
@@ -114,8 +103,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
.optional()
|
.optional()
|
||||||
.transform((val) => (val ? removeTrailingSlash(val) : undefined)),
|
.transform((val) => (val ? removeTrailingSlash(val) : undefined)),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).optional(),
|
enforcementLevel: z.nativeEnum(EnforcementLevel).optional(),
|
||||||
allowedSelfApprovals: z.boolean().default(true),
|
allowedSelfApprovals: z.boolean().default(true)
|
||||||
environments: z.array(z.string()).optional()
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@@ -6,7 +6,6 @@ import { registerAzureClientSecretRotationRouter } from "./azure-client-secret-r
|
|||||||
import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router";
|
import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router";
|
||||||
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
|
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
|
||||||
import { registerMySqlCredentialsRotationRouter } from "./mysql-credentials-rotation-router";
|
import { registerMySqlCredentialsRotationRouter } from "./mysql-credentials-rotation-router";
|
||||||
import { registerOktaClientSecretRotationRouter } from "./okta-client-secret-rotation-router";
|
|
||||||
import { registerOracleDBCredentialsRotationRouter } from "./oracledb-credentials-rotation-router";
|
import { registerOracleDBCredentialsRotationRouter } from "./oracledb-credentials-rotation-router";
|
||||||
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
|
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
|
||||||
|
|
||||||
@@ -23,6 +22,5 @@ export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record<
|
|||||||
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
|
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
|
||||||
[SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter,
|
[SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter,
|
||||||
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter,
|
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter,
|
||||||
[SecretRotation.LdapPassword]: registerLdapPasswordRotationRouter,
|
[SecretRotation.LdapPassword]: registerLdapPasswordRotationRouter
|
||||||
[SecretRotation.OktaClientSecret]: registerOktaClientSecretRotationRouter
|
|
||||||
};
|
};
|
||||||
|
@@ -1,19 +0,0 @@
|
|||||||
import {
|
|
||||||
CreateOktaClientSecretRotationSchema,
|
|
||||||
OktaClientSecretRotationGeneratedCredentialsSchema,
|
|
||||||
OktaClientSecretRotationSchema,
|
|
||||||
UpdateOktaClientSecretRotationSchema
|
|
||||||
} from "@app/ee/services/secret-rotation-v2/okta-client-secret";
|
|
||||||
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
|
||||||
|
|
||||||
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
|
|
||||||
|
|
||||||
export const registerOktaClientSecretRotationRouter = async (server: FastifyZodProvider) =>
|
|
||||||
registerSecretRotationEndpoints({
|
|
||||||
type: SecretRotation.OktaClientSecret,
|
|
||||||
server,
|
|
||||||
responseSchema: OktaClientSecretRotationSchema,
|
|
||||||
createSchema: CreateOktaClientSecretRotationSchema,
|
|
||||||
updateSchema: UpdateOktaClientSecretRotationSchema,
|
|
||||||
generatedCredentialsSchema: OktaClientSecretRotationGeneratedCredentialsSchema
|
|
||||||
});
|
|
@@ -315,12 +315,10 @@ export const registerSecretRotationEndpoints = <
|
|||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
deleteSecrets: z
|
deleteSecrets: z
|
||||||
.enum(["true", "false"])
|
.enum(["true", "false"])
|
||||||
.optional()
|
|
||||||
.transform((value) => value === "true")
|
.transform((value) => value === "true")
|
||||||
.describe(SecretRotations.DELETE(type).deleteSecrets),
|
.describe(SecretRotations.DELETE(type).deleteSecrets),
|
||||||
revokeGeneratedCredentials: z
|
revokeGeneratedCredentials: z
|
||||||
.enum(["true", "false"])
|
.enum(["true", "false"])
|
||||||
.optional()
|
|
||||||
.transform((value) => value === "true")
|
.transform((value) => value === "true")
|
||||||
.describe(SecretRotations.DELETE(type).revokeGeneratedCredentials)
|
.describe(SecretRotations.DELETE(type).revokeGeneratedCredentials)
|
||||||
}),
|
}),
|
||||||
|
@@ -7,7 +7,6 @@ import { AzureClientSecretRotationListItemSchema } from "@app/ee/services/secret
|
|||||||
import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||||
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||||
import { MySqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
|
import { MySqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
|
||||||
import { OktaClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/okta-client-secret";
|
|
||||||
import { OracleDBCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/oracledb-credentials";
|
import { OracleDBCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/oracledb-credentials";
|
||||||
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||||
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
||||||
@@ -24,8 +23,7 @@ const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
|
|||||||
Auth0ClientSecretRotationListItemSchema,
|
Auth0ClientSecretRotationListItemSchema,
|
||||||
AzureClientSecretRotationListItemSchema,
|
AzureClientSecretRotationListItemSchema,
|
||||||
AwsIamUserSecretRotationListItemSchema,
|
AwsIamUserSecretRotationListItemSchema,
|
||||||
LdapPasswordRotationListItemSchema,
|
LdapPasswordRotationListItemSchema
|
||||||
OktaClientSecretRotationListItemSchema
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
|
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
|
||||||
|
@@ -1,16 +0,0 @@
|
|||||||
import { registerSecretScanningEndpoints } from "@app/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-endpoints";
|
|
||||||
import {
|
|
||||||
CreateGitLabDataSourceSchema,
|
|
||||||
GitLabDataSourceSchema,
|
|
||||||
UpdateGitLabDataSourceSchema
|
|
||||||
} from "@app/ee/services/secret-scanning-v2/gitlab";
|
|
||||||
import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
|
||||||
|
|
||||||
export const registerGitLabSecretScanningRouter = async (server: FastifyZodProvider) =>
|
|
||||||
registerSecretScanningEndpoints({
|
|
||||||
type: SecretScanningDataSource.GitLab,
|
|
||||||
server,
|
|
||||||
responseSchema: GitLabDataSourceSchema,
|
|
||||||
createSchema: CreateGitLabDataSourceSchema,
|
|
||||||
updateSchema: UpdateGitLabDataSourceSchema
|
|
||||||
});
|
|
@@ -1,4 +1,3 @@
|
|||||||
import { registerGitLabSecretScanningRouter } from "@app/ee/routes/v2/secret-scanning-v2-routers/gitlab-secret-scanning-router";
|
|
||||||
import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
||||||
|
|
||||||
import { registerBitbucketSecretScanningRouter } from "./bitbucket-secret-scanning-router";
|
import { registerBitbucketSecretScanningRouter } from "./bitbucket-secret-scanning-router";
|
||||||
@@ -11,6 +10,5 @@ export const SECRET_SCANNING_REGISTER_ROUTER_MAP: Record<
|
|||||||
(server: FastifyZodProvider) => Promise<void>
|
(server: FastifyZodProvider) => Promise<void>
|
||||||
> = {
|
> = {
|
||||||
[SecretScanningDataSource.GitHub]: registerGitHubSecretScanningRouter,
|
[SecretScanningDataSource.GitHub]: registerGitHubSecretScanningRouter,
|
||||||
[SecretScanningDataSource.Bitbucket]: registerBitbucketSecretScanningRouter,
|
[SecretScanningDataSource.Bitbucket]: registerBitbucketSecretScanningRouter
|
||||||
[SecretScanningDataSource.GitLab]: registerGitLabSecretScanningRouter
|
|
||||||
};
|
};
|
||||||
|
@@ -4,7 +4,6 @@ import { SecretScanningConfigsSchema } from "@app/db/schemas";
|
|||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { BitbucketDataSourceListItemSchema } from "@app/ee/services/secret-scanning-v2/bitbucket";
|
import { BitbucketDataSourceListItemSchema } from "@app/ee/services/secret-scanning-v2/bitbucket";
|
||||||
import { GitHubDataSourceListItemSchema } from "@app/ee/services/secret-scanning-v2/github";
|
import { GitHubDataSourceListItemSchema } from "@app/ee/services/secret-scanning-v2/github";
|
||||||
import { GitLabDataSourceListItemSchema } from "@app/ee/services/secret-scanning-v2/gitlab";
|
|
||||||
import {
|
import {
|
||||||
SecretScanningFindingStatus,
|
SecretScanningFindingStatus,
|
||||||
SecretScanningScanStatus
|
SecretScanningScanStatus
|
||||||
@@ -25,8 +24,7 @@ import { AuthMode } from "@app/services/auth/auth-type";
|
|||||||
|
|
||||||
const SecretScanningDataSourceOptionsSchema = z.discriminatedUnion("type", [
|
const SecretScanningDataSourceOptionsSchema = z.discriminatedUnion("type", [
|
||||||
GitHubDataSourceListItemSchema,
|
GitHubDataSourceListItemSchema,
|
||||||
BitbucketDataSourceListItemSchema,
|
BitbucketDataSourceListItemSchema
|
||||||
GitLabDataSourceListItemSchema
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerSecretScanningV2Router = async (server: FastifyZodProvider) => {
|
export const registerSecretScanningV2Router = async (server: FastifyZodProvider) => {
|
||||||
|
@@ -26,7 +26,6 @@ export interface TAccessApprovalPolicyDALFactory
|
|||||||
>,
|
>,
|
||||||
customFilter?: {
|
customFilter?: {
|
||||||
policyId?: string;
|
policyId?: string;
|
||||||
envId?: string;
|
|
||||||
},
|
},
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
) => Promise<
|
) => Promise<
|
||||||
@@ -56,6 +55,11 @@ export interface TAccessApprovalPolicyDALFactory
|
|||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
|
environment: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
projectId: string;
|
projectId: string;
|
||||||
bypassers: (
|
bypassers: (
|
||||||
| {
|
| {
|
||||||
@@ -68,11 +72,6 @@ export interface TAccessApprovalPolicyDALFactory
|
|||||||
type: BypasserType.Group;
|
type: BypasserType.Group;
|
||||||
}
|
}
|
||||||
)[];
|
)[];
|
||||||
environments: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
}[];
|
|
||||||
}[]
|
}[]
|
||||||
>;
|
>;
|
||||||
findById: (
|
findById: (
|
||||||
@@ -96,11 +95,11 @@ export interface TAccessApprovalPolicyDALFactory
|
|||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
environments: {
|
environment: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
}[];
|
};
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
@@ -144,26 +143,6 @@ export interface TAccessApprovalPolicyDALFactory
|
|||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
>;
|
>;
|
||||||
findPolicyByEnvIdAndSecretPath: (
|
|
||||||
{ envIds, secretPath }: { envIds: string[]; secretPath: string },
|
|
||||||
tx?: Knex
|
|
||||||
) => Promise<{
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
approvals: number;
|
|
||||||
enforcementLevel: string;
|
|
||||||
allowedSelfApprovals: boolean;
|
|
||||||
secretPath: string;
|
|
||||||
deletedAt?: Date | null | undefined;
|
|
||||||
environments: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
}[];
|
|
||||||
projectId: string;
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TAccessApprovalPolicyServiceFactory {
|
export interface TAccessApprovalPolicyServiceFactory {
|
||||||
@@ -388,7 +367,6 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
|
|||||||
filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>,
|
filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>,
|
||||||
customFilter?: {
|
customFilter?: {
|
||||||
policyId?: string;
|
policyId?: string;
|
||||||
envId?: string;
|
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
const result = await tx(TableName.AccessApprovalPolicy)
|
const result = await tx(TableName.AccessApprovalPolicy)
|
||||||
@@ -399,17 +377,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
|
|||||||
void qb.where(`${TableName.AccessApprovalPolicy}.id`, "=", customFilter.policyId);
|
void qb.where(`${TableName.AccessApprovalPolicy}.id`, "=", customFilter.policyId);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.join(
|
.join(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
||||||
TableName.AccessApprovalPolicyEnvironment,
|
|
||||||
`${TableName.AccessApprovalPolicy}.id`,
|
|
||||||
`${TableName.AccessApprovalPolicyEnvironment}.policyId`
|
|
||||||
)
|
|
||||||
.join(TableName.Environment, `${TableName.AccessApprovalPolicyEnvironment}.envId`, `${TableName.Environment}.id`)
|
|
||||||
.where((qb) => {
|
|
||||||
if (customFilter?.envId) {
|
|
||||||
void qb.where(`${TableName.AccessApprovalPolicyEnvironment}.envId`, "=", customFilter.envId);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.AccessApprovalPolicyApprover,
|
TableName.AccessApprovalPolicyApprover,
|
||||||
`${TableName.AccessApprovalPolicy}.id`,
|
`${TableName.AccessApprovalPolicy}.id`,
|
||||||
@@ -436,7 +404,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
|
|||||||
.select(tx.ref("bypasserGroupId").withSchema(TableName.AccessApprovalPolicyBypasser))
|
.select(tx.ref("bypasserGroupId").withSchema(TableName.AccessApprovalPolicyBypasser))
|
||||||
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
|
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
|
||||||
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
|
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
|
||||||
.select(tx.ref("id").withSchema(TableName.Environment).as("environmentId"))
|
.select(tx.ref("id").withSchema(TableName.Environment).as("envId"))
|
||||||
.select(tx.ref("projectId").withSchema(TableName.Environment))
|
.select(tx.ref("projectId").withSchema(TableName.Environment))
|
||||||
.select(selectAllTableCols(TableName.AccessApprovalPolicy));
|
.select(selectAllTableCols(TableName.AccessApprovalPolicy));
|
||||||
|
|
||||||
@@ -480,15 +448,6 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
|
|||||||
sequence: approverSequence,
|
sequence: approverSequence,
|
||||||
approvalsRequired
|
approvalsRequired
|
||||||
})
|
})
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "environmentId",
|
|
||||||
label: "environments" as const,
|
|
||||||
mapper: ({ environmentId: id, envName, envSlug }) => ({
|
|
||||||
id,
|
|
||||||
name: envName,
|
|
||||||
slug: envSlug
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -511,6 +470,11 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
|
|||||||
data: docs,
|
data: docs,
|
||||||
key: "id",
|
key: "id",
|
||||||
parentMapper: (data) => ({
|
parentMapper: (data) => ({
|
||||||
|
environment: {
|
||||||
|
id: data.envId,
|
||||||
|
name: data.envName,
|
||||||
|
slug: data.envSlug
|
||||||
|
},
|
||||||
projectId: data.projectId,
|
projectId: data.projectId,
|
||||||
...AccessApprovalPoliciesSchema.parse(data)
|
...AccessApprovalPoliciesSchema.parse(data)
|
||||||
// secretPath: data.secretPath || undefined,
|
// secretPath: data.secretPath || undefined,
|
||||||
@@ -553,15 +517,6 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
|
|||||||
id,
|
id,
|
||||||
type: BypasserType.Group as const
|
type: BypasserType.Group as const
|
||||||
})
|
})
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "environmentId",
|
|
||||||
label: "environments" as const,
|
|
||||||
mapper: ({ environmentId: id, envName, envSlug }) => ({
|
|
||||||
id,
|
|
||||||
name: envName,
|
|
||||||
slug: envSlug
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -590,20 +545,14 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
buildFindFilter(
|
buildFindFilter(
|
||||||
{
|
{
|
||||||
|
envId,
|
||||||
secretPath
|
secretPath
|
||||||
},
|
},
|
||||||
TableName.AccessApprovalPolicy
|
TableName.AccessApprovalPolicy
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.join(
|
|
||||||
TableName.AccessApprovalPolicyEnvironment,
|
|
||||||
`${TableName.AccessApprovalPolicyEnvironment}.policyId`,
|
|
||||||
`${TableName.AccessApprovalPolicy}.id`
|
|
||||||
)
|
|
||||||
.where(`${TableName.AccessApprovalPolicyEnvironment}.envId`, "=", envId)
|
|
||||||
.orderBy("deletedAt", "desc")
|
.orderBy("deletedAt", "desc")
|
||||||
.orderByRaw(`"deletedAt" IS NULL`)
|
.orderByRaw(`"deletedAt" IS NULL`)
|
||||||
.select(selectAllTableCols(TableName.AccessApprovalPolicy))
|
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -612,81 +561,5 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const findPolicyByEnvIdAndSecretPath: TAccessApprovalPolicyDALFactory["findPolicyByEnvIdAndSecretPath"] = async (
|
return { ...accessApprovalPolicyOrm, find, findById, softDeleteById, findLastValidPolicy };
|
||||||
{ envIds, secretPath },
|
|
||||||
tx
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const docs = await (tx || db.replicaNode())(TableName.AccessApprovalPolicy)
|
|
||||||
.join(
|
|
||||||
TableName.AccessApprovalPolicyEnvironment,
|
|
||||||
`${TableName.AccessApprovalPolicyEnvironment}.policyId`,
|
|
||||||
`${TableName.AccessApprovalPolicy}.id`
|
|
||||||
)
|
|
||||||
.join(
|
|
||||||
TableName.Environment,
|
|
||||||
`${TableName.AccessApprovalPolicyEnvironment}.envId`,
|
|
||||||
`${TableName.Environment}.id`
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
buildFindFilter(
|
|
||||||
{
|
|
||||||
$in: {
|
|
||||||
envId: envIds
|
|
||||||
}
|
|
||||||
},
|
|
||||||
TableName.AccessApprovalPolicyEnvironment
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
buildFindFilter(
|
|
||||||
{
|
|
||||||
secretPath
|
|
||||||
},
|
|
||||||
TableName.AccessApprovalPolicy
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.whereNull(`${TableName.AccessApprovalPolicy}.deletedAt`)
|
|
||||||
.orderBy("deletedAt", "desc")
|
|
||||||
.orderByRaw(`"deletedAt" IS NULL`)
|
|
||||||
.select(selectAllTableCols(TableName.AccessApprovalPolicy))
|
|
||||||
.select(db.ref("name").withSchema(TableName.Environment).as("envName"))
|
|
||||||
.select(db.ref("slug").withSchema(TableName.Environment).as("envSlug"))
|
|
||||||
.select(db.ref("id").withSchema(TableName.Environment).as("environmentId"))
|
|
||||||
.select(db.ref("projectId").withSchema(TableName.Environment));
|
|
||||||
const formattedDocs = sqlNestRelationships({
|
|
||||||
data: docs,
|
|
||||||
key: "id",
|
|
||||||
parentMapper: (data) => ({
|
|
||||||
projectId: data.projectId,
|
|
||||||
...AccessApprovalPoliciesSchema.parse(data)
|
|
||||||
}),
|
|
||||||
childrenMapper: [
|
|
||||||
{
|
|
||||||
key: "environmentId",
|
|
||||||
label: "environments" as const,
|
|
||||||
mapper: ({ environmentId: id, envName, envSlug }) => ({
|
|
||||||
id,
|
|
||||||
name: envName,
|
|
||||||
slug: envSlug
|
|
||||||
})
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
return formattedDocs?.[0];
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({ error, name: "findPolicyByEnvIdAndSecretPath" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
...accessApprovalPolicyOrm,
|
|
||||||
find,
|
|
||||||
findById,
|
|
||||||
softDeleteById,
|
|
||||||
findLastValidPolicy,
|
|
||||||
findPolicyByEnvIdAndSecretPath
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
@@ -1,32 +0,0 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
|
||||||
import { TableName } from "@app/db/schemas";
|
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
|
||||||
import { buildFindFilter, ormify, selectAllTableCols } from "@app/lib/knex";
|
|
||||||
|
|
||||||
export type TAccessApprovalPolicyEnvironmentDALFactory = ReturnType<typeof accessApprovalPolicyEnvironmentDALFactory>;
|
|
||||||
|
|
||||||
export const accessApprovalPolicyEnvironmentDALFactory = (db: TDbClient) => {
|
|
||||||
const accessApprovalPolicyEnvironmentOrm = ormify(db, TableName.AccessApprovalPolicyEnvironment);
|
|
||||||
|
|
||||||
const findAvailablePoliciesByEnvId = async (envId: string, tx?: Knex) => {
|
|
||||||
try {
|
|
||||||
const docs = await (tx || db.replicaNode())(TableName.AccessApprovalPolicyEnvironment)
|
|
||||||
.join(
|
|
||||||
TableName.AccessApprovalPolicy,
|
|
||||||
`${TableName.AccessApprovalPolicyEnvironment}.policyId`,
|
|
||||||
`${TableName.AccessApprovalPolicy}.id`
|
|
||||||
)
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
.where(buildFindFilter({ envId }, TableName.AccessApprovalPolicyEnvironment))
|
|
||||||
.whereNull(`${TableName.AccessApprovalPolicy}.deletedAt`)
|
|
||||||
.select(selectAllTableCols(TableName.AccessApprovalPolicyEnvironment));
|
|
||||||
return docs;
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({ error, name: "findAvailablePoliciesByEnvId" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return { ...accessApprovalPolicyEnvironmentOrm, findAvailablePoliciesByEnvId };
|
|
||||||
};
|
|
@@ -1,6 +1,5 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@@ -21,7 +20,6 @@ import {
|
|||||||
TAccessApprovalPolicyBypasserDALFactory
|
TAccessApprovalPolicyBypasserDALFactory
|
||||||
} from "./access-approval-policy-approver-dal";
|
} from "./access-approval-policy-approver-dal";
|
||||||
import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
|
import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
|
||||||
import { TAccessApprovalPolicyEnvironmentDALFactory } from "./access-approval-policy-environment-dal";
|
|
||||||
import {
|
import {
|
||||||
ApproverType,
|
ApproverType,
|
||||||
BypasserType,
|
BypasserType,
|
||||||
@@ -46,14 +44,12 @@ type TAccessApprovalPolicyServiceFactoryDep = {
|
|||||||
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
||||||
accessApprovalRequestReviewerDAL: Pick<TAccessApprovalRequestReviewerDALFactory, "update" | "delete">;
|
accessApprovalRequestReviewerDAL: Pick<TAccessApprovalRequestReviewerDALFactory, "update" | "delete">;
|
||||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find">;
|
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find">;
|
||||||
accessApprovalPolicyEnvironmentDAL: TAccessApprovalPolicyEnvironmentDALFactory;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const accessApprovalPolicyServiceFactory = ({
|
export const accessApprovalPolicyServiceFactory = ({
|
||||||
accessApprovalPolicyDAL,
|
accessApprovalPolicyDAL,
|
||||||
accessApprovalPolicyApproverDAL,
|
accessApprovalPolicyApproverDAL,
|
||||||
accessApprovalPolicyBypasserDAL,
|
accessApprovalPolicyBypasserDAL,
|
||||||
accessApprovalPolicyEnvironmentDAL,
|
|
||||||
groupDAL,
|
groupDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
@@ -66,22 +62,21 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
}: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => {
|
}: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => {
|
||||||
const $policyExists = async ({
|
const $policyExists = async ({
|
||||||
envId,
|
envId,
|
||||||
envIds,
|
|
||||||
secretPath,
|
secretPath,
|
||||||
policyId
|
policyId
|
||||||
}: {
|
}: {
|
||||||
envId?: string;
|
envId: string;
|
||||||
envIds?: string[];
|
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
policyId?: string;
|
policyId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
if (!envId && !envIds) {
|
const policy = await accessApprovalPolicyDAL
|
||||||
throw new BadRequestError({ message: "Must provide either envId or envIds" });
|
.findOne({
|
||||||
}
|
envId,
|
||||||
const policy = await accessApprovalPolicyDAL.findPolicyByEnvIdAndSecretPath({
|
secretPath,
|
||||||
secretPath,
|
deletedAt: null
|
||||||
envIds: envId ? [envId] : (envIds as string[])
|
})
|
||||||
});
|
.catch(() => null);
|
||||||
|
|
||||||
return policyId ? policy && policy.id !== policyId : Boolean(policy);
|
return policyId ? policy && policy.id !== policyId : Boolean(policy);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,7 +92,6 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
bypassers,
|
bypassers,
|
||||||
projectSlug,
|
projectSlug,
|
||||||
environment,
|
environment,
|
||||||
environments,
|
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals,
|
allowedSelfApprovals,
|
||||||
approvalsRequired
|
approvalsRequired
|
||||||
@@ -122,31 +116,20 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
);
|
);
|
||||||
const mergedEnvs = (environment ? [environment] : environments) || [];
|
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
|
||||||
if (mergedEnvs.length === 0) {
|
if (!env) throw new NotFoundError({ message: `Environment with slug '${environment}' not found` });
|
||||||
throw new BadRequestError({ message: "Must provide either environment or environments" });
|
|
||||||
}
|
|
||||||
const envs = await projectEnvDAL.find({ $in: { slug: mergedEnvs }, projectId: project.id });
|
|
||||||
if (!envs.length || envs.length !== mergedEnvs.length) {
|
|
||||||
const notFoundEnvs = mergedEnvs.filter((env) => !envs.find((el) => el.slug === env));
|
|
||||||
throw new NotFoundError({ message: `One or more environments not found: ${notFoundEnvs.join(", ")}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const env of envs) {
|
if (await $policyExists({ envId: env.id, secretPath })) {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
throw new BadRequestError({
|
||||||
if (await $policyExists({ envId: env.id, secretPath })) {
|
message: `A policy for secret path '${secretPath}' already exists in environment '${environment}'`
|
||||||
throw new BadRequestError({
|
});
|
||||||
message: `A policy for secret path '${secretPath}' already exists in environment '${env.slug}'`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let approverUserIds = userApprovers;
|
let approverUserIds = userApprovers;
|
||||||
@@ -214,7 +197,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
const doc = await accessApprovalPolicyDAL.create(
|
const doc = await accessApprovalPolicyDAL.create(
|
||||||
{
|
{
|
||||||
envId: envs[0].id,
|
envId: env.id,
|
||||||
approvals,
|
approvals,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
@@ -223,10 +206,6 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
await accessApprovalPolicyEnvironmentDAL.insertMany(
|
|
||||||
envs.map((el) => ({ policyId: doc.id, envId: el.id })),
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
if (approverUserIds.length) {
|
if (approverUserIds.length) {
|
||||||
await accessApprovalPolicyApproverDAL.insertMany(
|
await accessApprovalPolicyApproverDAL.insertMany(
|
||||||
@@ -279,7 +258,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
return doc;
|
return doc;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { ...accessApproval, environments: envs, projectId: project.id, environment: envs[0] };
|
return { ...accessApproval, environment: env, projectId: project.id };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAccessApprovalPolicyByProjectSlug: TAccessApprovalPolicyServiceFactory["getAccessApprovalPolicyByProjectSlug"] =
|
const getAccessApprovalPolicyByProjectSlug: TAccessApprovalPolicyServiceFactory["getAccessApprovalPolicyByProjectSlug"] =
|
||||||
@@ -293,15 +272,11 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
|
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
|
||||||
return accessApprovalPolicies.map((policy) => ({
|
return accessApprovalPolicies;
|
||||||
...policy,
|
|
||||||
environment: policy.environments[0]
|
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["updateAccessApprovalPolicy"] = async ({
|
const updateAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["updateAccessApprovalPolicy"] = async ({
|
||||||
@@ -317,8 +292,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
approvals,
|
approvals,
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals,
|
allowedSelfApprovals,
|
||||||
approvalsRequired,
|
approvalsRequired
|
||||||
environments
|
|
||||||
}: TUpdateAccessApprovalPolicy) => {
|
}: TUpdateAccessApprovalPolicy) => {
|
||||||
const groupApprovers = approvers.filter((approver) => approver.type === ApproverType.Group);
|
const groupApprovers = approvers.filter((approver) => approver.type === ApproverType.Group);
|
||||||
|
|
||||||
@@ -346,27 +320,16 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||||
}
|
}
|
||||||
|
|
||||||
let envs = accessApprovalPolicy.environments;
|
|
||||||
if (
|
if (
|
||||||
environments &&
|
await $policyExists({
|
||||||
(environments.length !== envs.length || environments.some((env) => !envs.find((el) => el.slug === env)))
|
envId: accessApprovalPolicy.envId,
|
||||||
|
secretPath: secretPath || accessApprovalPolicy.secretPath,
|
||||||
|
policyId: accessApprovalPolicy.id
|
||||||
|
})
|
||||||
) {
|
) {
|
||||||
envs = await projectEnvDAL.find({ $in: { slug: environments }, projectId: accessApprovalPolicy.projectId });
|
throw new BadRequestError({
|
||||||
}
|
message: `A policy for secret path '${secretPath}' already exists in environment '${accessApprovalPolicy.environment.slug}'`
|
||||||
|
});
|
||||||
for (const env of envs) {
|
|
||||||
if (
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await $policyExists({
|
|
||||||
envId: env.id,
|
|
||||||
secretPath: secretPath || accessApprovalPolicy.secretPath,
|
|
||||||
policyId: accessApprovalPolicy.id
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `A policy for secret path '${secretPath || accessApprovalPolicy.secretPath}' already exists in environment '${env.slug}'`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
@@ -374,8 +337,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: accessApprovalPolicy.projectId,
|
projectId: accessApprovalPolicy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||||
@@ -522,14 +484,6 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (environments) {
|
|
||||||
await accessApprovalPolicyEnvironmentDAL.delete({ policyId: doc.id }, tx);
|
|
||||||
await accessApprovalPolicyEnvironmentDAL.insertMany(
|
|
||||||
envs.map((env) => ({ policyId: doc.id, envId: env.id })),
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await accessApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx);
|
await accessApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx);
|
||||||
|
|
||||||
if (bypasserUserIds.length) {
|
if (bypasserUserIds.length) {
|
||||||
@@ -559,8 +513,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...updatedPolicy,
|
...updatedPolicy,
|
||||||
environments: accessApprovalPolicy.environments,
|
environment: accessApprovalPolicy.environment,
|
||||||
environment: accessApprovalPolicy.environments[0],
|
|
||||||
projectId: accessApprovalPolicy.projectId
|
projectId: accessApprovalPolicy.projectId
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -580,8 +533,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: policy.projectId,
|
projectId: policy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
@@ -611,10 +563,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return policy;
|
||||||
...policy,
|
|
||||||
environment: policy.environments[0]
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAccessPolicyCountByEnvSlug: TAccessApprovalPolicyServiceFactory["getAccessPolicyCountByEnvSlug"] = async ({
|
const getAccessPolicyCountByEnvSlug: TAccessApprovalPolicyServiceFactory["getAccessPolicyCountByEnvSlug"] = async ({
|
||||||
@@ -634,8 +583,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
@@ -644,13 +592,11 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
|
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
|
||||||
if (!environment) throw new NotFoundError({ message: `Environment with slug '${envSlug}' not found` });
|
if (!environment) throw new NotFoundError({ message: `Environment with slug '${envSlug}' not found` });
|
||||||
|
|
||||||
const policies = await accessApprovalPolicyDAL.find(
|
const policies = await accessApprovalPolicyDAL.find({
|
||||||
{
|
envId: environment.id,
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
deletedAt: null
|
deletedAt: null
|
||||||
},
|
});
|
||||||
{ envId: environment.id }
|
|
||||||
);
|
|
||||||
if (!policies) throw new NotFoundError({ message: `No policies found in environment with slug '${envSlug}'` });
|
if (!policies) throw new NotFoundError({ message: `No policies found in environment with slug '${envSlug}'` });
|
||||||
|
|
||||||
return { count: policies.length };
|
return { count: policies.length };
|
||||||
@@ -676,16 +622,12 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: policy.projectId,
|
projectId: policy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
return {
|
return policy;
|
||||||
...policy,
|
|
||||||
environment: policy.environments[0]
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@@ -26,8 +26,7 @@ export enum BypasserType {
|
|||||||
export type TCreateAccessApprovalPolicy = {
|
export type TCreateAccessApprovalPolicy = {
|
||||||
approvals: number;
|
approvals: number;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
environment?: string;
|
environment: string;
|
||||||
environments?: string[];
|
|
||||||
approvers: (
|
approvers: (
|
||||||
| { type: ApproverType.Group; id: string; sequence?: number }
|
| { type: ApproverType.Group; id: string; sequence?: number }
|
||||||
| { type: ApproverType.User; id?: string; username?: string; sequence?: number }
|
| { type: ApproverType.User; id?: string; username?: string; sequence?: number }
|
||||||
@@ -59,7 +58,6 @@ export type TUpdateAccessApprovalPolicy = {
|
|||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
||||||
environments?: string[];
|
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TDeleteAccessApprovalPolicy = {
|
export type TDeleteAccessApprovalPolicy = {
|
||||||
@@ -115,15 +113,6 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
slug: string;
|
slug: string;
|
||||||
position: number;
|
position: number;
|
||||||
};
|
};
|
||||||
environments: {
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
projectId: string;
|
|
||||||
slug: string;
|
|
||||||
position: number;
|
|
||||||
}[];
|
|
||||||
projectId: string;
|
projectId: string;
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
@@ -164,11 +153,6 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
environments: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
}[];
|
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}>;
|
}>;
|
||||||
updateAccessApprovalPolicy: ({
|
updateAccessApprovalPolicy: ({
|
||||||
@@ -184,19 +168,13 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
approvals,
|
approvals,
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals,
|
allowedSelfApprovals,
|
||||||
approvalsRequired,
|
approvalsRequired
|
||||||
environments
|
|
||||||
}: TUpdateAccessApprovalPolicy) => Promise<{
|
}: TUpdateAccessApprovalPolicy) => Promise<{
|
||||||
environment: {
|
environment: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
environments: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
}[];
|
|
||||||
projectId: string;
|
projectId: string;
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
@@ -247,11 +225,6 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
environments: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
}[];
|
|
||||||
projectId: string;
|
projectId: string;
|
||||||
bypassers: (
|
bypassers: (
|
||||||
| {
|
| {
|
||||||
@@ -303,11 +276,6 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
environments: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
}[];
|
|
||||||
projectId: string;
|
projectId: string;
|
||||||
bypassers: (
|
bypassers: (
|
||||||
| {
|
| {
|
||||||
|
@@ -65,7 +65,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName
|
|||||||
deletedAt: Date | null | undefined;
|
deletedAt: Date | null | undefined;
|
||||||
};
|
};
|
||||||
projectId: string;
|
projectId: string;
|
||||||
environments: string[];
|
environment: string;
|
||||||
requestedByUser: {
|
requestedByUser: {
|
||||||
userId: string;
|
userId: string;
|
||||||
email: string | null | undefined;
|
email: string | null | undefined;
|
||||||
@@ -515,17 +515,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
|||||||
`accessApprovalReviewerUser.id`
|
`accessApprovalReviewerUser.id`
|
||||||
)
|
)
|
||||||
|
|
||||||
.leftJoin(
|
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
||||||
TableName.AccessApprovalPolicyEnvironment,
|
|
||||||
`${TableName.AccessApprovalPolicy}.id`,
|
|
||||||
`${TableName.AccessApprovalPolicyEnvironment}.policyId`
|
|
||||||
)
|
|
||||||
|
|
||||||
.leftJoin(
|
|
||||||
TableName.Environment,
|
|
||||||
`${TableName.AccessApprovalPolicyEnvironment}.envId`,
|
|
||||||
`${TableName.Environment}.id`
|
|
||||||
)
|
|
||||||
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
||||||
.select(
|
.select(
|
||||||
tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover),
|
tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover),
|
||||||
@@ -693,11 +683,6 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
|||||||
lastName,
|
lastName,
|
||||||
username
|
username
|
||||||
})
|
})
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "environment",
|
|
||||||
label: "environments" as const,
|
|
||||||
mapper: ({ environment }) => environment
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import msFn from "ms";
|
import msFn from "ms";
|
||||||
|
|
||||||
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
|
import { ProjectMembershipRole } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { groupBy } from "@app/lib/fn";
|
import { groupBy } from "@app/lib/fn";
|
||||||
@@ -86,25 +86,6 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
projectMicrosoftTeamsConfigDAL,
|
projectMicrosoftTeamsConfigDAL,
|
||||||
projectSlackConfigDAL
|
projectSlackConfigDAL
|
||||||
}: TSecretApprovalRequestServiceFactoryDep): TAccessApprovalRequestServiceFactory => {
|
}: TSecretApprovalRequestServiceFactoryDep): TAccessApprovalRequestServiceFactory => {
|
||||||
const $getEnvironmentFromPermissions = (permissions: unknown): string | null => {
|
|
||||||
if (!Array.isArray(permissions) || permissions.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstPermission = permissions[0] as unknown[];
|
|
||||||
if (!Array.isArray(firstPermission) || firstPermission.length < 3) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const metadata = firstPermission[2] as Record<string, unknown>;
|
|
||||||
if (typeof metadata === "object" && metadata !== null && "environment" in metadata) {
|
|
||||||
const env = metadata.environment;
|
|
||||||
return typeof env === "string" ? env : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createAccessApprovalRequest: TAccessApprovalRequestServiceFactory["createAccessApprovalRequest"] = async ({
|
const createAccessApprovalRequest: TAccessApprovalRequestServiceFactory["createAccessApprovalRequest"] = async ({
|
||||||
isTemporary,
|
isTemporary,
|
||||||
temporaryRange,
|
temporaryRange,
|
||||||
@@ -126,8 +107,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
@@ -236,7 +216,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`;
|
const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`;
|
||||||
const approvalUrl = `${cfg.SITE_URL}/projects/secret-management/${project.id}/approval`;
|
const approvalUrl = `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`;
|
||||||
|
|
||||||
await triggerWorkflowIntegrationNotification({
|
await triggerWorkflowIntegrationNotification({
|
||||||
input: {
|
input: {
|
||||||
@@ -309,8 +289,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
@@ -327,15 +306,6 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
requests = requests.filter((request) => request.environment === envSlug);
|
requests = requests.filter((request) => request.environment === envSlug);
|
||||||
}
|
}
|
||||||
|
|
||||||
requests = requests.map((request) => {
|
|
||||||
const permissionEnvironment = $getEnvironmentFromPermissions(request.permissions);
|
|
||||||
|
|
||||||
if (permissionEnvironment) {
|
|
||||||
request.environmentName = permissionEnvironment;
|
|
||||||
}
|
|
||||||
return request;
|
|
||||||
});
|
|
||||||
|
|
||||||
return { requests };
|
return { requests };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -353,34 +323,19 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
throw new NotFoundError({ message: `Secret approval request with ID '${requestId}' not found` });
|
throw new NotFoundError({ message: `Secret approval request with ID '${requestId}' not found` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { policy, environments, permissions } = accessApprovalRequest;
|
const { policy, environment } = accessApprovalRequest;
|
||||||
if (policy.deletedAt) {
|
if (policy.deletedAt) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "The policy associated with this access request has been deleted."
|
message: "The policy associated with this access request has been deleted."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const permissionEnvironment = $getEnvironmentFromPermissions(permissions);
|
|
||||||
if (
|
|
||||||
!permissionEnvironment ||
|
|
||||||
(!environments.includes(permissionEnvironment) && status === ApprovalStatus.APPROVED)
|
|
||||||
) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `The original policy ${policy.name} is not attached to environment '${permissionEnvironment}'.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const environment = await projectEnvDAL.findOne({
|
|
||||||
projectId: accessApprovalRequest.projectId,
|
|
||||||
slug: permissionEnvironment
|
|
||||||
});
|
|
||||||
|
|
||||||
const { membership, hasRole } = await permissionService.getProjectPermission({
|
const { membership, hasRole } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId: accessApprovalRequest.projectId,
|
projectId: accessApprovalRequest.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
@@ -595,8 +550,8 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
requesterEmail: actingUser.email,
|
requesterEmail: actingUser.email,
|
||||||
bypassReason: bypassReason || "No reason provided",
|
bypassReason: bypassReason || "No reason provided",
|
||||||
secretPath: policy.secretPath || "/",
|
secretPath: policy.secretPath || "/",
|
||||||
environment: environment?.name || permissionEnvironment,
|
environment,
|
||||||
approvalUrl: `${cfg.SITE_URL}/projects/secret-management/${project.id}/approval`,
|
approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`,
|
||||||
requestType: "access"
|
requestType: "access"
|
||||||
},
|
},
|
||||||
template: SmtpTemplates.AccessSecretRequestBypassed
|
template: SmtpTemplates.AccessSecretRequestBypassed
|
||||||
@@ -627,8 +582,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
|
@@ -45,10 +45,7 @@ export const ValidateOracleDBConnectionCredentialsSchema = z.discriminatedUnion(
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
export const CreateOracleDBConnectionSchema = ValidateOracleDBConnectionCredentialsSchema.and(
|
export const CreateOracleDBConnectionSchema = ValidateOracleDBConnectionCredentialsSchema.and(
|
||||||
GenericCreateAppConnectionFieldsSchema(AppConnection.OracleDB, {
|
GenericCreateAppConnectionFieldsSchema(AppConnection.OracleDB, { supportsPlatformManagedCredentials: true })
|
||||||
supportsPlatformManagedCredentials: true,
|
|
||||||
supportsGateways: true
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const UpdateOracleDBConnectionSchema = z
|
export const UpdateOracleDBConnectionSchema = z
|
||||||
@@ -57,12 +54,7 @@ export const UpdateOracleDBConnectionSchema = z
|
|||||||
AppConnections.UPDATE(AppConnection.OracleDB).credentials
|
AppConnections.UPDATE(AppConnection.OracleDB).credentials
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.and(
|
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.OracleDB, { supportsPlatformManagedCredentials: true }));
|
||||||
GenericUpdateAppConnectionFieldsSchema(AppConnection.OracleDB, {
|
|
||||||
supportsPlatformManagedCredentials: true,
|
|
||||||
supportsGateways: true
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
export const OracleDBConnectionListItemSchema = z.object({
|
export const OracleDBConnectionListItemSchema = z.object({
|
||||||
name: z.literal("OracleDB"),
|
name: z.literal("OracleDB"),
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@@ -38,8 +37,7 @@ export const assumePrivilegeServiceFactory = ({
|
|||||||
actorId: actorPermissionDetails.id,
|
actorId: actorPermissionDetails.id,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod: actorPermissionDetails.authMethod,
|
actorAuthMethod: actorPermissionDetails.authMethod,
|
||||||
actorOrgId: actorPermissionDetails.orgId,
|
actorOrgId: actorPermissionDetails.orgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (targetActorType === ActorType.USER) {
|
if (targetActorType === ActorType.USER) {
|
||||||
@@ -60,8 +58,7 @@ export const assumePrivilegeServiceFactory = ({
|
|||||||
actorId: targetActorId,
|
actorId: targetActorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod: actorPermissionDetails.authMethod,
|
actorAuthMethod: actorPermissionDetails.authMethod,
|
||||||
actorOrgId: actorPermissionDetails.orgId,
|
actorOrgId: actorPermissionDetails.orgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
import { AxiosError, RawAxiosRequestHeaders } from "axios";
|
import { AxiosError, RawAxiosRequestHeaders } from "axios";
|
||||||
|
|
||||||
import { ProjectType, SecretKeyEncoding } from "@app/db/schemas";
|
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||||
import { TEventBusService } from "@app/ee/services/event/event-bus-service";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { TopicName, toPublishableEvent } from "@app/ee/services/event/types";
|
|
||||||
import { request } from "@app/lib/config/request";
|
import { request } from "@app/lib/config/request";
|
||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
@@ -22,7 +21,6 @@ type TAuditLogQueueServiceFactoryDep = {
|
|||||||
queueService: TQueueServiceFactory;
|
queueService: TQueueServiceFactory;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
eventBusService: TEventBusService;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAuditLogQueueServiceFactory = {
|
export type TAuditLogQueueServiceFactory = {
|
||||||
@@ -38,18 +36,134 @@ export const auditLogQueueServiceFactory = async ({
|
|||||||
queueService,
|
queueService,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
auditLogStreamDAL,
|
auditLogStreamDAL
|
||||||
eventBusService
|
|
||||||
}: TAuditLogQueueServiceFactoryDep): Promise<TAuditLogQueueServiceFactory> => {
|
}: TAuditLogQueueServiceFactoryDep): Promise<TAuditLogQueueServiceFactory> => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
const pushToLog = async (data: TCreateAuditLogDTO) => {
|
const pushToLog = async (data: TCreateAuditLogDTO) => {
|
||||||
await queueService.queue<QueueName.AuditLog>(QueueName.AuditLog, QueueJobs.AuditLog, data, {
|
if (appCfg.USE_PG_QUEUE && appCfg.SHOULD_INIT_PG_QUEUE) {
|
||||||
removeOnFail: {
|
await queueService.queuePg<QueueName.AuditLog>(QueueJobs.AuditLog, data, {
|
||||||
count: 3
|
retryLimit: 10,
|
||||||
},
|
retryBackoff: true
|
||||||
removeOnComplete: true
|
});
|
||||||
});
|
} else {
|
||||||
|
await queueService.queue<QueueName.AuditLog>(QueueName.AuditLog, QueueJobs.AuditLog, data, {
|
||||||
|
removeOnFail: {
|
||||||
|
count: 3
|
||||||
|
},
|
||||||
|
removeOnComplete: true
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (appCfg.SHOULD_INIT_PG_QUEUE) {
|
||||||
|
await queueService.startPg<QueueName.AuditLog>(
|
||||||
|
QueueJobs.AuditLog,
|
||||||
|
async ([job]) => {
|
||||||
|
const { actor, event, ipAddress, projectId, userAgent, userAgentType } = job.data;
|
||||||
|
let { orgId } = job.data;
|
||||||
|
const MS_IN_DAY = 24 * 60 * 60 * 1000;
|
||||||
|
let project;
|
||||||
|
|
||||||
|
if (!orgId) {
|
||||||
|
// it will never be undefined for both org and project id
|
||||||
|
// TODO(akhilmhdh): use caching here in dal to avoid db calls
|
||||||
|
project = await projectDAL.findById(projectId as string);
|
||||||
|
orgId = project.orgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(orgId);
|
||||||
|
if (plan.auditLogsRetentionDays === 0) {
|
||||||
|
// skip inserting if audit log retention is 0 meaning its not supported
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For project actions, set TTL to project-level audit log retention config
|
||||||
|
// This condition ensures that the plan's audit log retention days cannot be bypassed
|
||||||
|
const ttlInDays =
|
||||||
|
project?.auditLogsRetentionDays && project.auditLogsRetentionDays < plan.auditLogsRetentionDays
|
||||||
|
? project.auditLogsRetentionDays
|
||||||
|
: plan.auditLogsRetentionDays;
|
||||||
|
|
||||||
|
const ttl = ttlInDays * MS_IN_DAY;
|
||||||
|
|
||||||
|
const auditLog = await auditLogDAL.create({
|
||||||
|
actor: actor.type,
|
||||||
|
actorMetadata: actor.metadata,
|
||||||
|
userAgent,
|
||||||
|
projectId,
|
||||||
|
projectName: project?.name,
|
||||||
|
ipAddress,
|
||||||
|
orgId,
|
||||||
|
eventType: event.type,
|
||||||
|
expiresAt: new Date(Date.now() + ttl),
|
||||||
|
eventMetadata: event.metadata,
|
||||||
|
userAgentType
|
||||||
|
});
|
||||||
|
|
||||||
|
const logStreams = orgId ? await auditLogStreamDAL.find({ orgId }) : [];
|
||||||
|
await Promise.allSettled(
|
||||||
|
logStreams.map(
|
||||||
|
async ({
|
||||||
|
url,
|
||||||
|
encryptedHeadersTag,
|
||||||
|
encryptedHeadersIV,
|
||||||
|
encryptedHeadersKeyEncoding,
|
||||||
|
encryptedHeadersCiphertext
|
||||||
|
}) => {
|
||||||
|
const streamHeaders =
|
||||||
|
encryptedHeadersIV && encryptedHeadersCiphertext && encryptedHeadersTag
|
||||||
|
? (JSON.parse(
|
||||||
|
crypto
|
||||||
|
.encryption()
|
||||||
|
.symmetric()
|
||||||
|
.decryptWithRootEncryptionKey({
|
||||||
|
keyEncoding: encryptedHeadersKeyEncoding as SecretKeyEncoding,
|
||||||
|
iv: encryptedHeadersIV,
|
||||||
|
tag: encryptedHeadersTag,
|
||||||
|
ciphertext: encryptedHeadersCiphertext
|
||||||
|
})
|
||||||
|
) as LogStreamHeaders[])
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const headers: RawAxiosRequestHeaders = { "Content-Type": "application/json" };
|
||||||
|
|
||||||
|
if (streamHeaders.length)
|
||||||
|
streamHeaders.forEach(({ key, value }) => {
|
||||||
|
headers[key] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await request.post(
|
||||||
|
url,
|
||||||
|
{ ...providerSpecificPayload(url), ...auditLog },
|
||||||
|
{
|
||||||
|
headers,
|
||||||
|
// request timeout
|
||||||
|
timeout: AUDIT_LOG_STREAM_TIMEOUT,
|
||||||
|
// connection timeout
|
||||||
|
signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
`Failed to stream audit log [url=${url}] for org [orgId=${orgId}] [error=${(error as AxiosError).message}]`
|
||||||
|
);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
batchSize: 1,
|
||||||
|
workerCount: 30,
|
||||||
|
pollingIntervalSeconds: 0.5
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
queueService.start(QueueName.AuditLog, async (job) => {
|
queueService.start(QueueName.AuditLog, async (job) => {
|
||||||
const { actor, event, ipAddress, projectId, userAgent, userAgentType } = job.data;
|
const { actor, event, ipAddress, projectId, userAgent, userAgentType } = job.data;
|
||||||
let { orgId } = job.data;
|
let { orgId } = job.data;
|
||||||
@@ -64,97 +178,88 @@ export const auditLogQueueServiceFactory = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
|
if (plan.auditLogsRetentionDays === 0) {
|
||||||
|
// skip inserting if audit log retention is 0 meaning its not supported
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// skip inserting if audit log retention is 0 meaning its not supported
|
// For project actions, set TTL to project-level audit log retention config
|
||||||
if (plan.auditLogsRetentionDays !== 0) {
|
// This condition ensures that the plan's audit log retention days cannot be bypassed
|
||||||
// For project actions, set TTL to project-level audit log retention config
|
const ttlInDays =
|
||||||
// This condition ensures that the plan's audit log retention days cannot be bypassed
|
project?.auditLogsRetentionDays && project.auditLogsRetentionDays < plan.auditLogsRetentionDays
|
||||||
const ttlInDays =
|
? project.auditLogsRetentionDays
|
||||||
project?.auditLogsRetentionDays && project.auditLogsRetentionDays < plan.auditLogsRetentionDays
|
: plan.auditLogsRetentionDays;
|
||||||
? project.auditLogsRetentionDays
|
|
||||||
: plan.auditLogsRetentionDays;
|
|
||||||
|
|
||||||
const ttl = ttlInDays * MS_IN_DAY;
|
const ttl = ttlInDays * MS_IN_DAY;
|
||||||
|
|
||||||
const auditLog = await auditLogDAL.create({
|
const auditLog = await auditLogDAL.create({
|
||||||
actor: actor.type,
|
actor: actor.type,
|
||||||
actorMetadata: actor.metadata,
|
actorMetadata: actor.metadata,
|
||||||
userAgent,
|
userAgent,
|
||||||
projectId,
|
projectId,
|
||||||
projectName: project?.name,
|
projectName: project?.name,
|
||||||
ipAddress,
|
ipAddress,
|
||||||
orgId,
|
orgId,
|
||||||
eventType: event.type,
|
eventType: event.type,
|
||||||
expiresAt: new Date(Date.now() + ttl),
|
expiresAt: new Date(Date.now() + ttl),
|
||||||
eventMetadata: event.metadata,
|
eventMetadata: event.metadata,
|
||||||
userAgentType
|
userAgentType
|
||||||
});
|
});
|
||||||
|
|
||||||
const logStreams = orgId ? await auditLogStreamDAL.find({ orgId }) : [];
|
const logStreams = orgId ? await auditLogStreamDAL.find({ orgId }) : [];
|
||||||
await Promise.allSettled(
|
await Promise.allSettled(
|
||||||
logStreams.map(
|
logStreams.map(
|
||||||
async ({
|
async ({
|
||||||
url,
|
url,
|
||||||
encryptedHeadersTag,
|
encryptedHeadersTag,
|
||||||
encryptedHeadersIV,
|
encryptedHeadersIV,
|
||||||
encryptedHeadersKeyEncoding,
|
encryptedHeadersKeyEncoding,
|
||||||
encryptedHeadersCiphertext
|
encryptedHeadersCiphertext
|
||||||
}) => {
|
}) => {
|
||||||
const streamHeaders =
|
const streamHeaders =
|
||||||
encryptedHeadersIV && encryptedHeadersCiphertext && encryptedHeadersTag
|
encryptedHeadersIV && encryptedHeadersCiphertext && encryptedHeadersTag
|
||||||
? (JSON.parse(
|
? (JSON.parse(
|
||||||
crypto
|
crypto
|
||||||
.encryption()
|
.encryption()
|
||||||
.symmetric()
|
.symmetric()
|
||||||
.decryptWithRootEncryptionKey({
|
.decryptWithRootEncryptionKey({
|
||||||
keyEncoding: encryptedHeadersKeyEncoding as SecretKeyEncoding,
|
keyEncoding: encryptedHeadersKeyEncoding as SecretKeyEncoding,
|
||||||
iv: encryptedHeadersIV,
|
iv: encryptedHeadersIV,
|
||||||
tag: encryptedHeadersTag,
|
tag: encryptedHeadersTag,
|
||||||
ciphertext: encryptedHeadersCiphertext
|
ciphertext: encryptedHeadersCiphertext
|
||||||
})
|
})
|
||||||
) as LogStreamHeaders[])
|
) as LogStreamHeaders[])
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const headers: RawAxiosRequestHeaders = { "Content-Type": "application/json" };
|
const headers: RawAxiosRequestHeaders = { "Content-Type": "application/json" };
|
||||||
|
|
||||||
if (streamHeaders.length)
|
if (streamHeaders.length)
|
||||||
streamHeaders.forEach(({ key, value }) => {
|
streamHeaders.forEach(({ key, value }) => {
|
||||||
headers[key] = value;
|
headers[key] = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await request.post(
|
const response = await request.post(
|
||||||
url,
|
url,
|
||||||
{ ...providerSpecificPayload(url), ...auditLog },
|
{ ...providerSpecificPayload(url), ...auditLog },
|
||||||
{
|
{
|
||||||
headers,
|
headers,
|
||||||
// request timeout
|
// request timeout
|
||||||
timeout: AUDIT_LOG_STREAM_TIMEOUT,
|
timeout: AUDIT_LOG_STREAM_TIMEOUT,
|
||||||
// connection timeout
|
// connection timeout
|
||||||
signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT)
|
signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Failed to stream audit log [url=${url}] for org [orgId=${orgId}] [error=${(error as AxiosError).message}]`
|
`Failed to stream audit log [url=${url}] for org [orgId=${orgId}] [error=${(error as AxiosError).message}]`
|
||||||
);
|
);
|
||||||
return error;
|
return error;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
);
|
)
|
||||||
}
|
);
|
||||||
|
|
||||||
const publishable = toPublishableEvent(event);
|
|
||||||
|
|
||||||
if (publishable) {
|
|
||||||
await eventBusService.publish(TopicName.CoreServers, {
|
|
||||||
type: ProjectType.SecretManager,
|
|
||||||
source: "infiscal",
|
|
||||||
data: publishable.data
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { requestContext } from "@fastify/request-context";
|
import { requestContext } from "@fastify/request-context";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
@@ -38,8 +37,7 @@ export const auditLogServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: filter.projectId,
|
projectId: filter.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
||||||
} else {
|
} else {
|
||||||
|
@@ -449,7 +449,6 @@ export enum EventType {
|
|||||||
PIT_REVERT_COMMIT = "pit-revert-commit",
|
PIT_REVERT_COMMIT = "pit-revert-commit",
|
||||||
PIT_GET_FOLDER_STATE = "pit-get-folder-state",
|
PIT_GET_FOLDER_STATE = "pit-get-folder-state",
|
||||||
PIT_COMPARE_FOLDER_STATES = "pit-compare-folder-states",
|
PIT_COMPARE_FOLDER_STATES = "pit-compare-folder-states",
|
||||||
PIT_PROCESS_NEW_COMMIT_RAW = "pit-process-new-commit-raw",
|
|
||||||
SECRET_SCANNING_DATA_SOURCE_LIST = "secret-scanning-data-source-list",
|
SECRET_SCANNING_DATA_SOURCE_LIST = "secret-scanning-data-source-list",
|
||||||
SECRET_SCANNING_DATA_SOURCE_CREATE = "secret-scanning-data-source-create",
|
SECRET_SCANNING_DATA_SOURCE_CREATE = "secret-scanning-data-source-create",
|
||||||
SECRET_SCANNING_DATA_SOURCE_UPDATE = "secret-scanning-data-source-update",
|
SECRET_SCANNING_DATA_SOURCE_UPDATE = "secret-scanning-data-source-update",
|
||||||
@@ -468,11 +467,7 @@ export enum EventType {
|
|||||||
|
|
||||||
CREATE_PROJECT = "create-project",
|
CREATE_PROJECT = "create-project",
|
||||||
UPDATE_PROJECT = "update-project",
|
UPDATE_PROJECT = "update-project",
|
||||||
DELETE_PROJECT = "delete-project",
|
DELETE_PROJECT = "delete-project"
|
||||||
|
|
||||||
CREATE_SECRET_REMINDER = "create-secret-reminder",
|
|
||||||
GET_SECRET_REMINDER = "get-secret-reminder",
|
|
||||||
DELETE_SECRET_REMINDER = "delete-secret-reminder"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const filterableSecretEvents: EventType[] = [
|
export const filterableSecretEvents: EventType[] = [
|
||||||
@@ -1551,9 +1546,8 @@ interface UpdateFolderEvent {
|
|||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
oldFolderName?: string;
|
oldFolderName: string;
|
||||||
newFolderName: string;
|
newFolderName: string;
|
||||||
newFolderDescription?: string;
|
|
||||||
folderPath: string;
|
folderPath: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -3228,18 +3222,6 @@ interface PitCompareFolderStatesEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PitProcessNewCommitRawEvent {
|
|
||||||
type: EventType.PIT_PROCESS_NEW_COMMIT_RAW;
|
|
||||||
metadata: {
|
|
||||||
projectId: string;
|
|
||||||
environment: string;
|
|
||||||
secretPath: string;
|
|
||||||
message: string;
|
|
||||||
approvalId?: string;
|
|
||||||
commitId?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SecretScanningDataSourceListEvent {
|
interface SecretScanningDataSourceListEvent {
|
||||||
type: EventType.SECRET_SCANNING_DATA_SOURCE_LIST;
|
type: EventType.SECRET_SCANNING_DATA_SOURCE_LIST;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -3330,31 +3312,6 @@ interface SecretScanningConfigUpdateEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SecretReminderCreateEvent {
|
|
||||||
type: EventType.CREATE_SECRET_REMINDER;
|
|
||||||
metadata: {
|
|
||||||
secretId: string;
|
|
||||||
message?: string | null;
|
|
||||||
repeatDays?: number | null;
|
|
||||||
nextReminderDate?: string | null;
|
|
||||||
recipients?: string[] | null;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SecretReminderGetEvent {
|
|
||||||
type: EventType.GET_SECRET_REMINDER;
|
|
||||||
metadata: {
|
|
||||||
secretId: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SecretReminderDeleteEvent {
|
|
||||||
type: EventType.DELETE_SECRET_REMINDER;
|
|
||||||
metadata: {
|
|
||||||
secretId: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SecretScanningConfigReadEvent {
|
interface SecretScanningConfigReadEvent {
|
||||||
type: EventType.SECRET_SCANNING_CONFIG_GET;
|
type: EventType.SECRET_SCANNING_CONFIG_GET;
|
||||||
metadata?: Record<string, never>; // not needed, based off projectId
|
metadata?: Record<string, never>; // not needed, based off projectId
|
||||||
@@ -3701,7 +3658,6 @@ export type Event =
|
|||||||
| PitRevertCommitEvent
|
| PitRevertCommitEvent
|
||||||
| PitCompareFolderStatesEvent
|
| PitCompareFolderStatesEvent
|
||||||
| PitGetFolderStateEvent
|
| PitGetFolderStateEvent
|
||||||
| PitProcessNewCommitRawEvent
|
|
||||||
| SecretScanningDataSourceListEvent
|
| SecretScanningDataSourceListEvent
|
||||||
| SecretScanningDataSourceGetEvent
|
| SecretScanningDataSourceGetEvent
|
||||||
| SecretScanningDataSourceCreateEvent
|
| SecretScanningDataSourceCreateEvent
|
||||||
@@ -3718,7 +3674,4 @@ export type Event =
|
|||||||
| OrgUpdateEvent
|
| OrgUpdateEvent
|
||||||
| ProjectCreateEvent
|
| ProjectCreateEvent
|
||||||
| ProjectUpdateEvent
|
| ProjectUpdateEvent
|
||||||
| ProjectDeleteEvent
|
| ProjectDeleteEvent;
|
||||||
| SecretReminderCreateEvent
|
|
||||||
| SecretReminderGetEvent
|
|
||||||
| SecretReminderDeleteEvent;
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
|
||||||
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
@@ -78,8 +77,7 @@ export const certificateAuthorityCrlServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: ca.projectId,
|
projectId: ca.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.CertificateManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import RE2 from "re2";
|
import RE2 from "re2";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import {
|
import {
|
||||||
@@ -85,8 +84,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
@@ -202,8 +200,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
@@ -300,8 +297,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
@@ -389,8 +385,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
@@ -437,8 +432,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import {
|
import {
|
||||||
@@ -79,8 +78,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
@@ -209,8 +207,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
@@ -361,8 +358,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
@@ -427,8 +423,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
@@ -492,8 +487,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// verify user has access to each env in request
|
// verify user has access to each env in request
|
||||||
@@ -536,8 +530,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
@@ -585,8 +578,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
@@ -623,8 +615,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod: actor.authMethod,
|
actorAuthMethod: actor.authMethod,
|
||||||
actorOrgId: actor.orgId,
|
actorOrgId: actor.orgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const userAccessibleFolderMappings = folderMappings.filter(({ path, environment }) =>
|
const userAccessibleFolderMappings = folderMappings.filter(({ path, environment }) =>
|
||||||
@@ -668,8 +659,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path);
|
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path);
|
||||||
|
@@ -1,83 +0,0 @@
|
|||||||
import Redis from "ioredis";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { logger } from "@app/lib/logger";
|
|
||||||
|
|
||||||
import { EventSchema, TopicName } from "./types";
|
|
||||||
|
|
||||||
export const eventBusFactory = (redis: Redis) => {
|
|
||||||
const publisher = redis.duplicate();
|
|
||||||
// Duplicate the publisher to create a subscriber.
|
|
||||||
// This is necessary because Redis does not allow a single connection to both publish and subscribe.
|
|
||||||
const subscriber = publisher.duplicate();
|
|
||||||
|
|
||||||
const init = async (topics: TopicName[] = Object.values(TopicName)) => {
|
|
||||||
subscriber.on("error", (e) => {
|
|
||||||
logger.error(e, "Event Bus subscriber error");
|
|
||||||
});
|
|
||||||
|
|
||||||
publisher.on("error", (e) => {
|
|
||||||
logger.error(e, "Event Bus publisher error");
|
|
||||||
});
|
|
||||||
|
|
||||||
await subscriber.subscribe(...topics);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Publishes an event to the specified topic.
|
|
||||||
* @param topic - The topic to publish the event to.
|
|
||||||
* @param event - The event data to publish.
|
|
||||||
*/
|
|
||||||
const publish = async <T extends z.input<typeof EventSchema>>(topic: TopicName, event: T) => {
|
|
||||||
const json = JSON.stringify(event);
|
|
||||||
|
|
||||||
return publisher.publish(topic, json, (err) => {
|
|
||||||
if (err) {
|
|
||||||
return logger.error(err, `Error publishing to channel ${topic}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param fn - The function to call when a message is received.
|
|
||||||
* It should accept the parsed event data as an argument.
|
|
||||||
* @template T - The type of the event data, which should match the schema defined in EventSchema.
|
|
||||||
* @returns A function that can be called to unsubscribe from the event bus.
|
|
||||||
*/
|
|
||||||
const subscribe = <T extends z.infer<typeof EventSchema>>(fn: (data: T) => Promise<void> | void) => {
|
|
||||||
// Not using async await cause redis client's `on` method does not expect async listeners.
|
|
||||||
const listener = (channel: string, message: string) => {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(message) as T;
|
|
||||||
const thenable = fn(parsed);
|
|
||||||
|
|
||||||
// If the function returns a Promise, catch any errors that occur during processing.
|
|
||||||
if (thenable instanceof Promise) {
|
|
||||||
thenable.catch((error) => {
|
|
||||||
logger.error(error, `Error processing message from channel ${channel}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error, `Error parsing message data from channel ${channel}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
subscriber.on("message", listener);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
subscriber.off("message", listener);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const close = async () => {
|
|
||||||
try {
|
|
||||||
await publisher.quit();
|
|
||||||
await subscriber.quit();
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error, "Error closing event bus connections");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return { init, publish, subscribe, close };
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TEventBusService = ReturnType<typeof eventBusFactory>;
|
|
@@ -1,164 +0,0 @@
|
|||||||
/* eslint-disable no-continue */
|
|
||||||
import { subject } from "@casl/ability";
|
|
||||||
import Redis from "ioredis";
|
|
||||||
|
|
||||||
import { KeyStorePrefixes } from "@app/keystore/keystore";
|
|
||||||
import { logger } from "@app/lib/logger";
|
|
||||||
|
|
||||||
import { TEventBusService } from "./event-bus-service";
|
|
||||||
import { createEventStreamClient, EventStreamClient, IEventStreamClientOpts } from "./event-sse-stream";
|
|
||||||
import { EventData, RegisteredEvent, toBusEventName } from "./types";
|
|
||||||
|
|
||||||
const AUTH_REFRESH_INTERVAL = 60 * 1000;
|
|
||||||
const HEART_BEAT_INTERVAL = 15 * 1000;
|
|
||||||
|
|
||||||
export const sseServiceFactory = (bus: TEventBusService, redis: Redis) => {
|
|
||||||
let heartbeatInterval: NodeJS.Timeout | null = null;
|
|
||||||
|
|
||||||
const clients = new Set<EventStreamClient>();
|
|
||||||
|
|
||||||
heartbeatInterval = setInterval(() => {
|
|
||||||
for (const client of clients) {
|
|
||||||
if (client.stream.closed) continue;
|
|
||||||
void client.ping();
|
|
||||||
}
|
|
||||||
}, HEART_BEAT_INTERVAL);
|
|
||||||
|
|
||||||
const refreshInterval = setInterval(() => {
|
|
||||||
for (const client of clients) {
|
|
||||||
if (client.stream.closed) continue;
|
|
||||||
void client.refresh();
|
|
||||||
}
|
|
||||||
}, AUTH_REFRESH_INTERVAL);
|
|
||||||
|
|
||||||
const removeActiveConnection = async (projectId: string, identityId: string, connectionId: string) => {
|
|
||||||
const set = KeyStorePrefixes.ActiveSSEConnectionsSet(projectId, identityId);
|
|
||||||
const key = KeyStorePrefixes.ActiveSSEConnections(projectId, identityId, connectionId);
|
|
||||||
|
|
||||||
await Promise.all([redis.lrem(set, 0, connectionId), redis.del(key)]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getActiveConnectionsCount = async (projectId: string, identityId: string) => {
|
|
||||||
const set = KeyStorePrefixes.ActiveSSEConnectionsSet(projectId, identityId);
|
|
||||||
const connections = await redis.lrange(set, 0, -1);
|
|
||||||
|
|
||||||
if (connections.length === 0) {
|
|
||||||
return 0; // No active connections
|
|
||||||
}
|
|
||||||
|
|
||||||
const keys = connections.map((c) => KeyStorePrefixes.ActiveSSEConnections(projectId, identityId, c));
|
|
||||||
|
|
||||||
const values = await redis.mget(...keys);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-plusplus
|
|
||||||
for (let i = 0; i < values.length; i++) {
|
|
||||||
if (values[i] === null) {
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await removeActiveConnection(projectId, identityId, connections[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return redis.llen(set);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDisconnect = async (client: EventStreamClient) => {
|
|
||||||
try {
|
|
||||||
client.close();
|
|
||||||
clients.delete(client);
|
|
||||||
await removeActiveConnection(client.auth.projectId, client.auth.actorId, client.id);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error, "Error during SSE stream disconnection");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function filterEventsForClient(client: EventStreamClient, event: EventData, registered: RegisteredEvent[]) {
|
|
||||||
const eventType = toBusEventName(event.data.eventType);
|
|
||||||
const match = registered.find((r) => r.event === eventType);
|
|
||||||
if (!match) return;
|
|
||||||
|
|
||||||
const item = event.data.payload;
|
|
||||||
|
|
||||||
if (Array.isArray(item)) {
|
|
||||||
if (item.length === 0) return;
|
|
||||||
|
|
||||||
const baseSubject = {
|
|
||||||
eventType,
|
|
||||||
environment: undefined as string | undefined,
|
|
||||||
secretPath: undefined as string | undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
const filtered = item.filter((ev) => {
|
|
||||||
baseSubject.secretPath = ev.secretPath ?? "/";
|
|
||||||
baseSubject.environment = ev.environment;
|
|
||||||
|
|
||||||
return client.matcher.can("subscribe", subject(event.type, baseSubject));
|
|
||||||
});
|
|
||||||
|
|
||||||
if (filtered.length === 0) return;
|
|
||||||
|
|
||||||
return client.send({
|
|
||||||
...event,
|
|
||||||
data: {
|
|
||||||
...event.data,
|
|
||||||
payload: filtered
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// For single item
|
|
||||||
const baseSubject = {
|
|
||||||
eventType,
|
|
||||||
secretPath: item.secretPath ?? "/",
|
|
||||||
environment: item.environment
|
|
||||||
};
|
|
||||||
|
|
||||||
if (client.matcher.can("subscribe", subject(event.type, baseSubject))) {
|
|
||||||
client.send(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const subscribe = async (
|
|
||||||
opts: IEventStreamClientOpts & {
|
|
||||||
onClose?: () => void;
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
const client = createEventStreamClient(redis, opts);
|
|
||||||
|
|
||||||
// Set up event listener on event bus
|
|
||||||
const unsubscribe = bus.subscribe((event) => {
|
|
||||||
if (event.type !== opts.type) return;
|
|
||||||
filterEventsForClient(client, event, opts.registered);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.stream.on("close", () => {
|
|
||||||
unsubscribe();
|
|
||||||
void onDisconnect(client); // This will never throw
|
|
||||||
});
|
|
||||||
|
|
||||||
await client.open();
|
|
||||||
|
|
||||||
clients.add(client);
|
|
||||||
|
|
||||||
return client;
|
|
||||||
};
|
|
||||||
|
|
||||||
const close = () => {
|
|
||||||
if (heartbeatInterval) {
|
|
||||||
clearInterval(heartbeatInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (refreshInterval) {
|
|
||||||
clearInterval(refreshInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const client of clients) {
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
clients.clear();
|
|
||||||
};
|
|
||||||
|
|
||||||
return { subscribe, close, getActiveConnectionsCount };
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TServerSentEventsService = ReturnType<typeof sseServiceFactory>;
|
|
@@ -1,178 +0,0 @@
|
|||||||
/* eslint-disable no-underscore-dangle */
|
|
||||||
import { Readable } from "node:stream";
|
|
||||||
|
|
||||||
import { MongoAbility, PureAbility } from "@casl/ability";
|
|
||||||
import { MongoQuery } from "@ucast/mongo2js";
|
|
||||||
import Redis from "ioredis";
|
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
|
|
||||||
import { ProjectType } from "@app/db/schemas";
|
|
||||||
import { ProjectPermissionSet } from "@app/ee/services/permission/project-permission";
|
|
||||||
import { KeyStorePrefixes } from "@app/keystore/keystore";
|
|
||||||
import { conditionsMatcher } from "@app/lib/casl";
|
|
||||||
import { logger } from "@app/lib/logger";
|
|
||||||
|
|
||||||
import { EventData, RegisteredEvent } from "./types";
|
|
||||||
|
|
||||||
export const getServerSentEventsHeaders = () =>
|
|
||||||
({
|
|
||||||
"Cache-Control": "no-cache",
|
|
||||||
"Content-Type": "text/event-stream",
|
|
||||||
Connection: "keep-alive",
|
|
||||||
"X-Accel-Buffering": "no"
|
|
||||||
}) as const;
|
|
||||||
|
|
||||||
type TAuthInfo = {
|
|
||||||
actorId: string;
|
|
||||||
projectId: string;
|
|
||||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface IEventStreamClientOpts {
|
|
||||||
type: ProjectType;
|
|
||||||
registered: RegisteredEvent[];
|
|
||||||
onAuthRefresh: (info: TAuthInfo) => Promise<void> | void;
|
|
||||||
getAuthInfo: () => Promise<TAuthInfo> | TAuthInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EventMessage {
|
|
||||||
time?: string | number;
|
|
||||||
type: string;
|
|
||||||
data?: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
function serializeSseEvent(chunk: EventMessage): string {
|
|
||||||
let payload = "";
|
|
||||||
|
|
||||||
if (chunk.time) payload += `id: ${chunk.time}\n`;
|
|
||||||
if (chunk.type) payload += `event: ${chunk.type}\n`;
|
|
||||||
if (chunk.data) payload += `data: ${JSON.stringify(chunk)}\n`;
|
|
||||||
|
|
||||||
return `${payload}\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EventStreamClient = {
|
|
||||||
id: string;
|
|
||||||
stream: Readable;
|
|
||||||
open: () => Promise<void>;
|
|
||||||
send: (data: EventMessage | EventData) => void;
|
|
||||||
ping: () => Promise<void>;
|
|
||||||
refresh: () => Promise<void>;
|
|
||||||
close: () => void;
|
|
||||||
get auth(): TAuthInfo;
|
|
||||||
signal: AbortSignal;
|
|
||||||
abort: () => void;
|
|
||||||
matcher: PureAbility;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function createEventStreamClient(redis: Redis, options: IEventStreamClientOpts): EventStreamClient {
|
|
||||||
const rules = options.registered.map((r) => ({
|
|
||||||
subject: options.type,
|
|
||||||
action: "subscribe",
|
|
||||||
conditions: {
|
|
||||||
eventType: r.event,
|
|
||||||
secretPath: r.conditions?.secretPath ?? "/",
|
|
||||||
environment: r.conditions?.environmentSlug
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
const id = `sse-${nanoid()}`;
|
|
||||||
const control = new AbortController();
|
|
||||||
const matcher = new PureAbility(rules, { conditionsMatcher });
|
|
||||||
|
|
||||||
let auth: TAuthInfo | undefined;
|
|
||||||
|
|
||||||
const stream = new Readable({
|
|
||||||
objectMode: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// We will manually push data to the stream
|
|
||||||
stream._read = () => {};
|
|
||||||
|
|
||||||
const send = (data: EventMessage | EventData) => {
|
|
||||||
const chunk = serializeSseEvent(data);
|
|
||||||
if (!stream.push(chunk)) {
|
|
||||||
logger.debug("Backpressure detected: dropped manual event");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
stream.on("error", (error: Error) => stream.destroy(error));
|
|
||||||
|
|
||||||
const open = async () => {
|
|
||||||
auth = await options.getAuthInfo();
|
|
||||||
await options.onAuthRefresh(auth);
|
|
||||||
|
|
||||||
const { actorId, projectId } = auth;
|
|
||||||
const set = KeyStorePrefixes.ActiveSSEConnectionsSet(projectId, actorId);
|
|
||||||
const key = KeyStorePrefixes.ActiveSSEConnections(projectId, actorId, id);
|
|
||||||
|
|
||||||
await Promise.all([redis.rpush(set, id), redis.set(key, "1", "EX", 60)]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ping = async () => {
|
|
||||||
if (!auth) return; // Avoid race condition if ping is called before open
|
|
||||||
|
|
||||||
const { actorId, projectId } = auth;
|
|
||||||
const key = KeyStorePrefixes.ActiveSSEConnections(projectId, actorId, id);
|
|
||||||
|
|
||||||
await redis.set(key, "1", "EX", 60);
|
|
||||||
|
|
||||||
stream.push("1");
|
|
||||||
};
|
|
||||||
|
|
||||||
const close = () => {
|
|
||||||
if (stream.closed) return;
|
|
||||||
stream.push(null);
|
|
||||||
stream.destroy();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refreshes the connection's auth permissions
|
|
||||||
* Must be called atleast once when connection is opened
|
|
||||||
*/
|
|
||||||
const refresh = async () => {
|
|
||||||
try {
|
|
||||||
auth = await options.getAuthInfo();
|
|
||||||
await options.onAuthRefresh(auth);
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
send({
|
|
||||||
type: "error",
|
|
||||||
data: {
|
|
||||||
...error
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return close();
|
|
||||||
}
|
|
||||||
stream.emit("error", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const abort = () => {
|
|
||||||
try {
|
|
||||||
control.abort();
|
|
||||||
} catch (error) {
|
|
||||||
logger.debug(error, "Error aborting SSE stream");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
stream,
|
|
||||||
open,
|
|
||||||
send,
|
|
||||||
ping,
|
|
||||||
refresh,
|
|
||||||
close,
|
|
||||||
signal: control.signal,
|
|
||||||
abort,
|
|
||||||
matcher,
|
|
||||||
get auth() {
|
|
||||||
if (!auth) {
|
|
||||||
throw new Error("Auth info not set");
|
|
||||||
}
|
|
||||||
|
|
||||||
return auth;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@@ -1,125 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { ProjectType } from "@app/db/schemas";
|
|
||||||
import { Event, EventType } from "@app/ee/services/audit-log/audit-log-types";
|
|
||||||
|
|
||||||
export enum TopicName {
|
|
||||||
CoreServers = "infisical::core-servers"
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum BusEventName {
|
|
||||||
CreateSecret = "secret:create",
|
|
||||||
UpdateSecret = "secret:update",
|
|
||||||
DeleteSecret = "secret:delete"
|
|
||||||
}
|
|
||||||
|
|
||||||
type PublisableEventTypes =
|
|
||||||
| EventType.CREATE_SECRET
|
|
||||||
| EventType.CREATE_SECRETS
|
|
||||||
| EventType.DELETE_SECRET
|
|
||||||
| EventType.DELETE_SECRETS
|
|
||||||
| EventType.UPDATE_SECRETS
|
|
||||||
| EventType.UPDATE_SECRET;
|
|
||||||
|
|
||||||
export function toBusEventName(input: EventType) {
|
|
||||||
switch (input) {
|
|
||||||
case EventType.CREATE_SECRET:
|
|
||||||
case EventType.CREATE_SECRETS:
|
|
||||||
return BusEventName.CreateSecret;
|
|
||||||
case EventType.UPDATE_SECRET:
|
|
||||||
case EventType.UPDATE_SECRETS:
|
|
||||||
return BusEventName.UpdateSecret;
|
|
||||||
case EventType.DELETE_SECRET:
|
|
||||||
case EventType.DELETE_SECRETS:
|
|
||||||
return BusEventName.DeleteSecret;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isBulkEvent = (event: Event): event is Extract<Event, { metadata: { secrets: Array<unknown> } }> => {
|
|
||||||
return event.type.endsWith("-secrets"); // Feels so wrong
|
|
||||||
};
|
|
||||||
|
|
||||||
export const toPublishableEvent = (event: Event) => {
|
|
||||||
const name = toBusEventName(event.type);
|
|
||||||
|
|
||||||
if (!name) return null;
|
|
||||||
|
|
||||||
const e = event as Extract<Event, { type: PublisableEventTypes }>;
|
|
||||||
|
|
||||||
if (isBulkEvent(e)) {
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
isBulk: true,
|
|
||||||
data: {
|
|
||||||
eventType: e.type,
|
|
||||||
payload: e.metadata.secrets.map((s) => ({
|
|
||||||
environment: e.metadata.environment,
|
|
||||||
secretPath: e.metadata.secretPath,
|
|
||||||
...s
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
isBulk: false,
|
|
||||||
data: {
|
|
||||||
eventType: e.type,
|
|
||||||
payload: {
|
|
||||||
...e.metadata,
|
|
||||||
environment: e.metadata.environment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} as const;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EventName = z.nativeEnum(BusEventName);
|
|
||||||
|
|
||||||
const EventSecretPayload = z.object({
|
|
||||||
secretPath: z.string().optional(),
|
|
||||||
secretId: z.string(),
|
|
||||||
secretKey: z.string(),
|
|
||||||
environment: z.string()
|
|
||||||
});
|
|
||||||
|
|
||||||
export type EventSecret = z.infer<typeof EventSecretPayload>;
|
|
||||||
|
|
||||||
export const EventSchema = z.object({
|
|
||||||
datacontenttype: z.literal("application/json").optional().default("application/json"),
|
|
||||||
type: z.nativeEnum(ProjectType),
|
|
||||||
source: z.string(),
|
|
||||||
time: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.default(() => new Date().toISOString()),
|
|
||||||
data: z.discriminatedUnion("eventType", [
|
|
||||||
z.object({
|
|
||||||
specversion: z.number().optional().default(1),
|
|
||||||
eventType: z.enum([EventType.CREATE_SECRET, EventType.UPDATE_SECRET, EventType.DELETE_SECRET]),
|
|
||||||
payload: EventSecretPayload
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
specversion: z.number().optional().default(1),
|
|
||||||
eventType: z.enum([EventType.CREATE_SECRETS, EventType.UPDATE_SECRETS, EventType.DELETE_SECRETS]),
|
|
||||||
payload: EventSecretPayload.array()
|
|
||||||
})
|
|
||||||
// Add more event types as needed
|
|
||||||
])
|
|
||||||
});
|
|
||||||
|
|
||||||
export type EventData = z.infer<typeof EventSchema>;
|
|
||||||
|
|
||||||
export const EventRegisterSchema = z.object({
|
|
||||||
event: EventName,
|
|
||||||
conditions: z
|
|
||||||
.object({
|
|
||||||
secretPath: z.string().optional().default("/"),
|
|
||||||
environmentSlug: z.string()
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
export type RegisteredEvent = z.infer<typeof EventRegisterSchema>;
|
|
@@ -566,14 +566,6 @@ export const gatewayServiceFactory = ({
|
|||||||
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${gatewayId} not found.` });
|
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${gatewayId} not found.` });
|
||||||
|
|
||||||
const orgGatewayConfig = await orgGatewayConfigDAL.findById(gateway.orgGatewayRootCaId);
|
const orgGatewayConfig = await orgGatewayConfigDAL.findById(gateway.orgGatewayRootCaId);
|
||||||
|
|
||||||
const orgLicensePlan = await licenseService.getPlan(orgGatewayConfig.orgId);
|
|
||||||
if (!orgLicensePlan.gateway) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Please upgrade your instance to Infisical's Enterprise plan to use gateways."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { decryptor: orgKmsDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const { decryptor: orgKmsDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.Organization,
|
type: KmsDataKey.Organization,
|
||||||
orgId: orgGatewayConfig.orgId
|
orgId: orgGatewayConfig.orgId
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import { packRules } from "@casl/ability/extra";
|
import { packRules } from "@casl/ability/extra";
|
||||||
|
|
||||||
import { ActionProjectType, TableName } from "@app/db/schemas";
|
import { TableName } from "@app/db/schemas";
|
||||||
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
||||||
import { ms } from "@app/lib/ms";
|
import { ms } from "@app/lib/ms";
|
||||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||||
@@ -61,8 +61,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionIdentityActions.Edit,
|
ProjectPermissionIdentityActions.Edit,
|
||||||
@@ -73,8 +72,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorId: identityId,
|
actorId: identityId,
|
||||||
projectId: identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
@@ -160,8 +158,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionIdentityActions.Edit,
|
ProjectPermissionIdentityActions.Edit,
|
||||||
@@ -172,8 +169,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorId: identityProjectMembership.identityId,
|
actorId: identityProjectMembership.identityId,
|
||||||
projectId: identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
@@ -260,8 +256,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionIdentityActions.Edit,
|
ProjectPermissionIdentityActions.Edit,
|
||||||
@@ -272,8 +267,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorId: identityProjectMembership.identityId,
|
actorId: identityProjectMembership.identityId,
|
||||||
projectId: identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
membership.shouldUseNewPrivilegeSystem,
|
||||||
@@ -321,8 +315,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionIdentityActions.Read,
|
ProjectPermissionIdentityActions.Read,
|
||||||
@@ -356,8 +349,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionIdentityActions.Read,
|
ProjectPermissionIdentityActions.Read,
|
||||||
@@ -392,8 +384,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionIdentityActions.Read,
|
ProjectPermissionIdentityActions.Read,
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability";
|
import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability";
|
||||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
|
||||||
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
||||||
import { ms } from "@app/lib/ms";
|
import { ms } from "@app/lib/ms";
|
||||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||||
@@ -73,8 +72,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
@@ -87,8 +85,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorId: identityId,
|
actorId: identityId,
|
||||||
projectId: identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
@@ -175,8 +172,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
@@ -189,8 +185,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorId: identityProjectMembership.identityId,
|
actorId: identityProjectMembership.identityId,
|
||||||
projectId: identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
@@ -293,8 +288,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionIdentityActions.Edit,
|
ProjectPermissionIdentityActions.Edit,
|
||||||
@@ -306,8 +300,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorId: identityProjectMembership.identityId,
|
actorId: identityProjectMembership.identityId,
|
||||||
projectId: identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
membership.shouldUseNewPrivilegeSystem,
|
||||||
@@ -366,8 +359,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionIdentityActions.Read,
|
ProjectPermissionIdentityActions.Read,
|
||||||
@@ -409,8 +401,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
|
||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||||
import { isValidIp } from "@app/lib/ip";
|
import { isValidIp } from "@app/lib/ip";
|
||||||
@@ -79,8 +78,7 @@ export const kmipServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.KMS
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
@@ -133,8 +131,7 @@ export const kmipServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: kmipClient.projectId,
|
projectId: kmipClient.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.KMS
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
@@ -165,8 +162,7 @@ export const kmipServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: kmipClient.projectId,
|
projectId: kmipClient.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.KMS
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
@@ -199,8 +195,7 @@ export const kmipServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: kmipClient.projectId,
|
projectId: kmipClient.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.KMS
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip);
|
||||||
@@ -221,8 +216,7 @@ export const kmipServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.KMS
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip);
|
||||||
@@ -258,8 +252,7 @@ export const kmipServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: kmipClient.projectId,
|
projectId: kmipClient.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.KMS
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
@@ -59,8 +59,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
secretScanning: false,
|
secretScanning: false,
|
||||||
enterpriseSecretSyncs: false,
|
enterpriseSecretSyncs: false,
|
||||||
enterpriseAppConnections: false,
|
enterpriseAppConnections: false,
|
||||||
fips: false,
|
fips: false
|
||||||
eventSubscriptions: false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setupLicenseRequestWithStore = (
|
export const setupLicenseRequestWithStore = (
|
||||||
|
@@ -5,14 +5,13 @@
|
|||||||
// TODO(akhilmhdh): With tony find out the api structure and fill it here
|
// TODO(akhilmhdh): With tony find out the api structure and fill it here
|
||||||
|
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { AxiosError } from "axios";
|
|
||||||
import { CronJob } from "cron";
|
import { CronJob } from "cron";
|
||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { verifyOfflineLicense } from "@app/lib/crypto";
|
import { verifyOfflineLicense } from "@app/lib/crypto";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { TIdentityOrgDALFactory } from "@app/services/identity/identity-org-dal";
|
import { TIdentityOrgDALFactory } from "@app/services/identity/identity-org-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
@@ -604,22 +603,10 @@ export const licenseServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const { data } = await licenseServerCloudApi.request.delete(
|
||||||
const { data } = await licenseServerCloudApi.request.delete(
|
`/api/license-server/v1/customers/${organization.customerId}/billing-details/payment-methods/${pmtMethodId}`
|
||||||
`/api/license-server/v1/customers/${organization.customerId}/billing-details/payment-methods/${pmtMethodId}`
|
);
|
||||||
);
|
return data;
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof AxiosError) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
||||||
message: `Failed to remove payment method: ${error.response?.data?.message}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Unable to remove payment method"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOrgTaxIds = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgTaxIdDTO) => {
|
const getOrgTaxIds = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgTaxIdDTO) => {
|
||||||
|
@@ -76,7 +76,6 @@ export type TFeatureSet = {
|
|||||||
enterpriseSecretSyncs: false;
|
enterpriseSecretSyncs: false;
|
||||||
enterpriseAppConnections: false;
|
enterpriseAppConnections: false;
|
||||||
fips: false;
|
fips: false;
|
||||||
eventSubscriptions: false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TOrgPlansTableDTO = {
|
export type TOrgPlansTableDTO = {
|
||||||
|
@@ -161,8 +161,7 @@ const buildAdminPermissionRules = () => {
|
|||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
ProjectPermissionSecretActions.Create,
|
ProjectPermissionSecretActions.Create,
|
||||||
ProjectPermissionSecretActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
ProjectPermissionSecretActions.Delete,
|
ProjectPermissionSecretActions.Delete
|
||||||
ProjectPermissionSecretActions.Subscribe
|
|
||||||
],
|
],
|
||||||
ProjectPermissionSub.Secrets
|
ProjectPermissionSub.Secrets
|
||||||
);
|
);
|
||||||
@@ -266,8 +265,7 @@ const buildMemberPermissionRules = () => {
|
|||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
ProjectPermissionSecretActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
ProjectPermissionSecretActions.Create,
|
ProjectPermissionSecretActions.Create,
|
||||||
ProjectPermissionSecretActions.Delete,
|
ProjectPermissionSecretActions.Delete
|
||||||
ProjectPermissionSecretActions.Subscribe
|
|
||||||
],
|
],
|
||||||
ProjectPermissionSub.Secrets
|
ProjectPermissionSub.Secrets
|
||||||
);
|
);
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { MongoAbility, RawRuleOf } from "@casl/ability";
|
import { MongoAbility, RawRuleOf } from "@casl/ability";
|
||||||
import { MongoQuery } from "@ucast/mongo2js";
|
import { MongoQuery } from "@ucast/mongo2js";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
|
||||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
import { OrgPermissionSet } from "./org-permission";
|
import { OrgPermissionSet } from "./org-permission";
|
||||||
@@ -21,7 +20,6 @@ export type TGetUserProjectPermissionArg = {
|
|||||||
userId: string;
|
userId: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
authMethod: ActorAuthMethod;
|
authMethod: ActorAuthMethod;
|
||||||
actionProjectType: ActionProjectType;
|
|
||||||
userOrgId?: string;
|
userOrgId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -29,14 +27,12 @@ export type TGetIdentityProjectPermissionArg = {
|
|||||||
identityId: string;
|
identityId: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
identityOrgId?: string;
|
identityOrgId?: string;
|
||||||
actionProjectType: ActionProjectType;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGetServiceTokenProjectPermissionArg = {
|
export type TGetServiceTokenProjectPermissionArg = {
|
||||||
serviceTokenId: string;
|
serviceTokenId: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
actorOrgId?: string;
|
actorOrgId?: string;
|
||||||
actionProjectType: ActionProjectType;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGetProjectPermissionArg = {
|
export type TGetProjectPermissionArg = {
|
||||||
@@ -45,7 +41,6 @@ export type TGetProjectPermissionArg = {
|
|||||||
projectId: string;
|
projectId: string;
|
||||||
actorAuthMethod: ActorAuthMethod;
|
actorAuthMethod: ActorAuthMethod;
|
||||||
actorOrgId?: string;
|
actorOrgId?: string;
|
||||||
actionProjectType: ActionProjectType;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TPermissionServiceFactory = {
|
export type TPermissionServiceFactory = {
|
||||||
@@ -143,13 +138,7 @@ export type TPermissionServiceFactory = {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
getUserProjectPermission: ({
|
getUserProjectPermission: ({ userId, projectId, authMethod, userOrgId }: TGetUserProjectPermissionArg) => Promise<{
|
||||||
userId,
|
|
||||||
projectId,
|
|
||||||
authMethod,
|
|
||||||
userOrgId,
|
|
||||||
actionProjectType
|
|
||||||
}: TGetUserProjectPermissionArg) => Promise<{
|
|
||||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||||
membership: {
|
membership: {
|
||||||
id: string;
|
id: string;
|
||||||
|
@@ -5,7 +5,6 @@ import { MongoQuery } from "@ucast/mongo2js";
|
|||||||
import handlebars from "handlebars";
|
import handlebars from "handlebars";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ActionProjectType,
|
|
||||||
OrgMembershipRole,
|
OrgMembershipRole,
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ServiceTokenScopes,
|
ServiceTokenScopes,
|
||||||
@@ -214,8 +213,7 @@ export const permissionServiceFactory = ({
|
|||||||
userId,
|
userId,
|
||||||
projectId,
|
projectId,
|
||||||
authMethod,
|
authMethod,
|
||||||
userOrgId,
|
userOrgId
|
||||||
actionProjectType
|
|
||||||
}: TGetUserProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.USER>> => {
|
}: TGetUserProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.USER>> => {
|
||||||
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
|
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
|
||||||
if (!userProjectPermission) throw new ForbiddenRequestError({ name: "User not a part of the specified project" });
|
if (!userProjectPermission) throw new ForbiddenRequestError({ name: "User not a part of the specified project" });
|
||||||
@@ -242,12 +240,6 @@ export const permissionServiceFactory = ({
|
|||||||
userProjectPermission.orgRole
|
userProjectPermission.orgRole
|
||||||
);
|
);
|
||||||
|
|
||||||
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== userProjectPermission.projectType) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `The project is of type ${userProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// join two permissions and pass to build the final permission set
|
// join two permissions and pass to build the final permission set
|
||||||
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||||
const additionalPrivileges =
|
const additionalPrivileges =
|
||||||
@@ -295,8 +287,7 @@ export const permissionServiceFactory = ({
|
|||||||
const getIdentityProjectPermission = async ({
|
const getIdentityProjectPermission = async ({
|
||||||
identityId,
|
identityId,
|
||||||
projectId,
|
projectId,
|
||||||
identityOrgId,
|
identityOrgId
|
||||||
actionProjectType
|
|
||||||
}: TGetIdentityProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
}: TGetIdentityProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
||||||
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
||||||
if (!identityProjectPermission)
|
if (!identityProjectPermission)
|
||||||
@@ -316,12 +307,6 @@ export const permissionServiceFactory = ({
|
|||||||
throw new ForbiddenRequestError({ name: "Identity is not a member of the specified organization" });
|
throw new ForbiddenRequestError({ name: "Identity is not a member of the specified organization" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== identityProjectPermission.projectType) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `The project is of type ${identityProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const rolePermissions =
|
const rolePermissions =
|
||||||
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||||
const additionalPrivileges =
|
const additionalPrivileges =
|
||||||
@@ -376,8 +361,7 @@ export const permissionServiceFactory = ({
|
|||||||
const getServiceTokenProjectPermission = async ({
|
const getServiceTokenProjectPermission = async ({
|
||||||
serviceTokenId,
|
serviceTokenId,
|
||||||
projectId,
|
projectId,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType
|
|
||||||
}: TGetServiceTokenProjectPermissionArg) => {
|
}: TGetServiceTokenProjectPermissionArg) => {
|
||||||
const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
|
const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
|
||||||
if (!serviceToken) throw new NotFoundError({ message: `Service token with ID '${serviceTokenId}' not found` });
|
if (!serviceToken) throw new NotFoundError({ message: `Service token with ID '${serviceTokenId}' not found` });
|
||||||
@@ -402,12 +386,6 @@ export const permissionServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== serviceTokenProject.type) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `The project is of type ${serviceTokenProject.type}. Operations of type ${actionProjectType} are not allowed.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
|
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
|
||||||
return {
|
return {
|
||||||
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
|
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
|
||||||
@@ -559,8 +537,7 @@ export const permissionServiceFactory = ({
|
|||||||
actorId: inputActorId,
|
actorId: inputActorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType
|
|
||||||
}: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => {
|
}: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => {
|
||||||
let actor = inputActor;
|
let actor = inputActor;
|
||||||
let actorId = inputActorId;
|
let actorId = inputActorId;
|
||||||
@@ -581,22 +558,19 @@ export const permissionServiceFactory = ({
|
|||||||
userId: actorId,
|
userId: actorId,
|
||||||
projectId,
|
projectId,
|
||||||
authMethod: actorAuthMethod,
|
authMethod: actorAuthMethod,
|
||||||
userOrgId: actorOrgId,
|
userOrgId: actorOrgId
|
||||||
actionProjectType
|
|
||||||
}) as Promise<TProjectPermissionRT<T>>;
|
}) as Promise<TProjectPermissionRT<T>>;
|
||||||
case ActorType.SERVICE:
|
case ActorType.SERVICE:
|
||||||
return getServiceTokenProjectPermission({
|
return getServiceTokenProjectPermission({
|
||||||
serviceTokenId: actorId,
|
serviceTokenId: actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType
|
|
||||||
}) as Promise<TProjectPermissionRT<T>>;
|
}) as Promise<TProjectPermissionRT<T>>;
|
||||||
case ActorType.IDENTITY:
|
case ActorType.IDENTITY:
|
||||||
return getIdentityProjectPermission({
|
return getIdentityProjectPermission({
|
||||||
identityId: actorId,
|
identityId: actorId,
|
||||||
projectId,
|
projectId,
|
||||||
identityOrgId: actorOrgId,
|
identityOrgId: actorOrgId
|
||||||
actionProjectType
|
|
||||||
}) as Promise<TProjectPermissionRT<T>>;
|
}) as Promise<TProjectPermissionRT<T>>;
|
||||||
default:
|
default:
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
|
@@ -36,8 +36,7 @@ export enum ProjectPermissionSecretActions {
|
|||||||
ReadValue = "readValue",
|
ReadValue = "readValue",
|
||||||
Create = "create",
|
Create = "create",
|
||||||
Edit = "edit",
|
Edit = "edit",
|
||||||
Delete = "delete",
|
Delete = "delete"
|
||||||
Subscribe = "subscribe"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionCmekActions {
|
export enum ProjectPermissionCmekActions {
|
||||||
@@ -205,7 +204,6 @@ export type SecretSubjectFields = {
|
|||||||
secretPath: string;
|
secretPath: string;
|
||||||
secretName?: string;
|
secretName?: string;
|
||||||
secretTags?: string[];
|
secretTags?: string[];
|
||||||
eventType?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SecretFolderSubjectFields = {
|
export type SecretFolderSubjectFields = {
|
||||||
@@ -485,17 +483,7 @@ const SecretConditionV2Schema = z
|
|||||||
.object({
|
.object({
|
||||||
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
|
||||||
})
|
})
|
||||||
.partial(),
|
.partial()
|
||||||
eventType: z.union([
|
|
||||||
z.string(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
|
||||||
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
|
||||||
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
|
|
||||||
})
|
|
||||||
.partial()
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
.partial();
|
.partial();
|
||||||
|
|
||||||
|
@@ -1,53 +1,29 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
|
||||||
import { Event, EventType } from "@app/ee/services/audit-log/audit-log-types";
|
|
||||||
import { ProjectPermissionCommitsActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionCommitsActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||||
import { TFolderCommitDALFactory } from "@app/services/folder-commit/folder-commit-dal";
|
import { ResourceType, TFolderCommitServiceFactory } from "@app/services/folder-commit/folder-commit-service";
|
||||||
import {
|
|
||||||
ResourceType,
|
|
||||||
TCommitResourceChangeDTO,
|
|
||||||
TFolderCommitServiceFactory
|
|
||||||
} from "@app/services/folder-commit/folder-commit-service";
|
|
||||||
import {
|
import {
|
||||||
isFolderCommitChange,
|
isFolderCommitChange,
|
||||||
isSecretCommitChange
|
isSecretCommitChange
|
||||||
} from "@app/services/folder-commit-changes/folder-commit-changes-dal";
|
} from "@app/services/folder-commit-changes/folder-commit-changes-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
|
||||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||||
import { TSecretServiceFactory } from "@app/services/secret/secret-service";
|
import { TSecretServiceFactory } from "@app/services/secret/secret-service";
|
||||||
import { TProcessNewCommitRawDTO } from "@app/services/secret/secret-types";
|
|
||||||
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||||
import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service";
|
import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service";
|
||||||
import { TSecretV2BridgeServiceFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-service";
|
|
||||||
import { SecretOperations, SecretUpdateMode } from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
|
||||||
|
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||||
import { TSecretApprovalPolicyServiceFactory } from "../secret-approval-policy/secret-approval-policy-service";
|
|
||||||
import { TSecretApprovalRequestServiceFactory } from "../secret-approval-request/secret-approval-request-service";
|
|
||||||
|
|
||||||
type TPitServiceFactoryDep = {
|
type TPitServiceFactoryDep = {
|
||||||
folderCommitService: TFolderCommitServiceFactory;
|
folderCommitService: TFolderCommitServiceFactory;
|
||||||
secretService: Pick<TSecretServiceFactory, "getSecretVersionsV2ByIds" | "getChangeVersions">;
|
secretService: Pick<TSecretServiceFactory, "getSecretVersionsV2ByIds" | "getChangeVersions">;
|
||||||
folderService: Pick<
|
folderService: Pick<TSecretFolderServiceFactory, "getFolderById" | "getFolderVersions">;
|
||||||
TSecretFolderServiceFactory,
|
|
||||||
"getFolderById" | "getFolderVersions" | "createManyFolders" | "updateManyFolders" | "deleteManyFolders"
|
|
||||||
>;
|
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
folderDAL: Pick<TSecretFolderDALFactory, "findSecretPathByFolderIds" | "findBySecretPath">;
|
folderDAL: Pick<TSecretFolderDALFactory, "findSecretPathByFolderIds">;
|
||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||||
secretApprovalRequestService: Pick<
|
|
||||||
TSecretApprovalRequestServiceFactory,
|
|
||||||
"generateSecretApprovalRequest" | "generateSecretApprovalRequestV2Bridge"
|
|
||||||
>;
|
|
||||||
secretApprovalPolicyService: Pick<TSecretApprovalPolicyServiceFactory, "getSecretApprovalPolicy">;
|
|
||||||
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus" | "findProjectBySlug" | "findById">;
|
|
||||||
secretV2BridgeService: TSecretV2BridgeServiceFactory;
|
|
||||||
folderCommitDAL: Pick<TFolderCommitDALFactory, "transaction">;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TPitServiceFactory = ReturnType<typeof pitServiceFactory>;
|
export type TPitServiceFactory = ReturnType<typeof pitServiceFactory>;
|
||||||
@@ -58,12 +34,7 @@ export const pitServiceFactory = ({
|
|||||||
folderService,
|
folderService,
|
||||||
permissionService,
|
permissionService,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL
|
||||||
secretApprovalRequestService,
|
|
||||||
secretApprovalPolicyService,
|
|
||||||
projectDAL,
|
|
||||||
secretV2BridgeService,
|
|
||||||
folderCommitDAL
|
|
||||||
}: TPitServiceFactoryDep) => {
|
}: TPitServiceFactoryDep) => {
|
||||||
const getCommitsCount = async ({
|
const getCommitsCount = async ({
|
||||||
actor,
|
actor,
|
||||||
@@ -349,8 +320,7 @@ export const pitServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(userPermission).throwUnlessCan(
|
ForbiddenError.from(userPermission).throwUnlessCan(
|
||||||
@@ -501,347 +471,6 @@ export const pitServiceFactory = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const processNewCommitRaw = async ({
|
|
||||||
actorId,
|
|
||||||
projectId,
|
|
||||||
environment,
|
|
||||||
actor,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
secretPath,
|
|
||||||
message,
|
|
||||||
changes = {
|
|
||||||
secrets: {
|
|
||||||
create: [],
|
|
||||||
update: [],
|
|
||||||
delete: []
|
|
||||||
},
|
|
||||||
folders: {
|
|
||||||
create: [],
|
|
||||||
update: [],
|
|
||||||
delete: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}: {
|
|
||||||
actorId: string;
|
|
||||||
projectId: string;
|
|
||||||
environment: string;
|
|
||||||
actor: ActorType;
|
|
||||||
actorOrgId: string;
|
|
||||||
actorAuthMethod: ActorAuthMethod;
|
|
||||||
secretPath: string;
|
|
||||||
message: string;
|
|
||||||
changes: TProcessNewCommitRawDTO;
|
|
||||||
}) => {
|
|
||||||
const policy =
|
|
||||||
actor === ActorType.USER
|
|
||||||
? await secretApprovalPolicyService.getSecretApprovalPolicy(projectId, environment, secretPath)
|
|
||||||
: undefined;
|
|
||||||
const secretMutationEvents: Event[] = [];
|
|
||||||
|
|
||||||
const project = await projectDAL.findById(projectId);
|
|
||||||
if (project.enforceCapitalization) {
|
|
||||||
const caseViolatingSecretKeys = [
|
|
||||||
// Check create operations
|
|
||||||
...(changes.secrets?.create
|
|
||||||
?.filter((sec) => sec.secretKey !== sec.secretKey.toUpperCase())
|
|
||||||
.map((sec) => sec.secretKey) ?? []),
|
|
||||||
|
|
||||||
// Check update operations
|
|
||||||
...(changes.secrets?.update
|
|
||||||
?.filter((sec) => sec.newSecretName && sec.newSecretName !== sec.newSecretName.toUpperCase())
|
|
||||||
.map((sec) => sec.secretKey) ?? [])
|
|
||||||
];
|
|
||||||
|
|
||||||
if (caseViolatingSecretKeys.length) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Secret names must be in UPPERCASE per project requirements: ${caseViolatingSecretKeys.join(
|
|
||||||
", "
|
|
||||||
)}. You can disable this requirement in project settings`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await folderCommitDAL.transaction(async (trx) => {
|
|
||||||
const targetFolder = await folderDAL.findBySecretPath(projectId, environment, secretPath, trx);
|
|
||||||
if (!targetFolder)
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Folder with path '${secretPath}' in environment with slug '${environment}' not found`,
|
|
||||||
name: "CreateManySecret"
|
|
||||||
});
|
|
||||||
const commitChanges: TCommitResourceChangeDTO[] = [];
|
|
||||||
const folderChanges: { create: string[]; update: string[]; delete: string[] } = {
|
|
||||||
create: [],
|
|
||||||
update: [],
|
|
||||||
delete: []
|
|
||||||
};
|
|
||||||
|
|
||||||
if ((changes.folders?.create?.length ?? 0) > 0) {
|
|
||||||
const createdFolders = await folderService.createManyFolders({
|
|
||||||
projectId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
folders:
|
|
||||||
changes.folders?.create?.map((folder) => ({
|
|
||||||
name: folder.folderName,
|
|
||||||
environment,
|
|
||||||
path: secretPath,
|
|
||||||
description: folder.description
|
|
||||||
})) ?? [],
|
|
||||||
tx: trx,
|
|
||||||
commitChanges
|
|
||||||
});
|
|
||||||
const newFolderEvents = createdFolders.folders.map(
|
|
||||||
(folder) =>
|
|
||||||
({
|
|
||||||
type: EventType.CREATE_FOLDER,
|
|
||||||
metadata: {
|
|
||||||
environment,
|
|
||||||
folderId: folder.id,
|
|
||||||
folderName: folder.name,
|
|
||||||
folderPath: secretPath,
|
|
||||||
...(folder.description ? { description: folder.description } : {})
|
|
||||||
}
|
|
||||||
}) as Event
|
|
||||||
);
|
|
||||||
secretMutationEvents.push(...newFolderEvents);
|
|
||||||
folderChanges.create.push(...createdFolders.folders.map((folder) => folder.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((changes.folders?.update?.length ?? 0) > 0) {
|
|
||||||
const updatedFolders = await folderService.updateManyFolders({
|
|
||||||
projectId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
folders:
|
|
||||||
changes.folders?.update?.map((folder) => ({
|
|
||||||
environment,
|
|
||||||
path: secretPath,
|
|
||||||
id: folder.id,
|
|
||||||
name: folder.folderName,
|
|
||||||
description: folder.description
|
|
||||||
})) ?? [],
|
|
||||||
tx: trx,
|
|
||||||
commitChanges
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedFolderEvents = updatedFolders.newFolders.map(
|
|
||||||
(folder) =>
|
|
||||||
({
|
|
||||||
type: EventType.UPDATE_FOLDER,
|
|
||||||
metadata: {
|
|
||||||
environment,
|
|
||||||
folderId: folder.id,
|
|
||||||
folderPath: secretPath,
|
|
||||||
newFolderName: folder.name,
|
|
||||||
newFolderDescription: folder.description
|
|
||||||
}
|
|
||||||
}) as Event
|
|
||||||
);
|
|
||||||
secretMutationEvents.push(...updatedFolderEvents);
|
|
||||||
folderChanges.update.push(...updatedFolders.newFolders.map((folder) => folder.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((changes.folders?.delete?.length ?? 0) > 0) {
|
|
||||||
const deletedFolders = await folderService.deleteManyFolders({
|
|
||||||
projectId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
folders:
|
|
||||||
changes.folders?.delete?.map((folder) => ({
|
|
||||||
environment,
|
|
||||||
path: secretPath,
|
|
||||||
idOrName: folder.id
|
|
||||||
})) ?? [],
|
|
||||||
tx: trx,
|
|
||||||
commitChanges
|
|
||||||
});
|
|
||||||
const deletedFolderEvents = deletedFolders.folders.map(
|
|
||||||
(folder) =>
|
|
||||||
({
|
|
||||||
type: EventType.DELETE_FOLDER,
|
|
||||||
metadata: {
|
|
||||||
environment,
|
|
||||||
folderId: folder.id,
|
|
||||||
folderPath: secretPath,
|
|
||||||
folderName: folder.name
|
|
||||||
}
|
|
||||||
}) as Event
|
|
||||||
);
|
|
||||||
secretMutationEvents.push(...deletedFolderEvents);
|
|
||||||
folderChanges.delete.push(...deletedFolders.folders.map((folder) => folder.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (policy) {
|
|
||||||
if (
|
|
||||||
(changes.secrets?.create?.length ?? 0) > 0 ||
|
|
||||||
(changes.secrets?.update?.length ?? 0) > 0 ||
|
|
||||||
(changes.secrets?.delete?.length ?? 0) > 0
|
|
||||||
) {
|
|
||||||
const approval = await secretApprovalRequestService.generateSecretApprovalRequestV2Bridge({
|
|
||||||
policy,
|
|
||||||
secretPath,
|
|
||||||
environment,
|
|
||||||
projectId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
data: {
|
|
||||||
[SecretOperations.Create]:
|
|
||||||
changes.secrets?.create?.map((el) => ({
|
|
||||||
tagIds: el.tagIds,
|
|
||||||
secretValue: el.secretValue,
|
|
||||||
secretComment: el.secretComment,
|
|
||||||
metadata: el.metadata,
|
|
||||||
skipMultilineEncoding: el.skipMultilineEncoding,
|
|
||||||
secretKey: el.secretKey,
|
|
||||||
secretMetadata: el.secretMetadata
|
|
||||||
})) ?? [],
|
|
||||||
[SecretOperations.Update]:
|
|
||||||
changes.secrets?.update?.map((el) => ({
|
|
||||||
tagIds: el.tagIds,
|
|
||||||
newSecretName: el.newSecretName,
|
|
||||||
secretValue: el.secretValue,
|
|
||||||
secretComment: el.secretComment,
|
|
||||||
metadata: el.metadata,
|
|
||||||
skipMultilineEncoding: el.skipMultilineEncoding,
|
|
||||||
secretKey: el.secretKey,
|
|
||||||
secretMetadata: el.secretMetadata
|
|
||||||
})) ?? [],
|
|
||||||
[SecretOperations.Delete]:
|
|
||||||
changes.secrets?.delete?.map((el) => ({
|
|
||||||
secretKey: el.secretKey
|
|
||||||
})) ?? []
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
approvalId: approval.id,
|
|
||||||
folderChanges,
|
|
||||||
secretMutationEvents
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
folderChanges,
|
|
||||||
secretMutationEvents
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((changes.secrets?.create?.length ?? 0) > 0) {
|
|
||||||
const newSecrets = await secretV2BridgeService.createManySecret({
|
|
||||||
secretPath,
|
|
||||||
environment,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
secrets: changes.secrets?.create ?? [],
|
|
||||||
tx: trx,
|
|
||||||
commitChanges
|
|
||||||
});
|
|
||||||
secretMutationEvents.push({
|
|
||||||
type: EventType.CREATE_SECRETS,
|
|
||||||
metadata: {
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
secrets: newSecrets.map((secret) => ({
|
|
||||||
secretId: secret.id,
|
|
||||||
secretKey: secret.secretKey,
|
|
||||||
secretVersion: secret.version
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if ((changes.secrets?.update?.length ?? 0) > 0) {
|
|
||||||
const updatedSecrets = await secretV2BridgeService.updateManySecret({
|
|
||||||
secretPath,
|
|
||||||
environment,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
secrets: changes.secrets?.update ?? [],
|
|
||||||
mode: SecretUpdateMode.FailOnNotFound,
|
|
||||||
tx: trx,
|
|
||||||
commitChanges
|
|
||||||
});
|
|
||||||
secretMutationEvents.push({
|
|
||||||
type: EventType.UPDATE_SECRETS,
|
|
||||||
metadata: {
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
secrets: updatedSecrets.map((secret) => ({
|
|
||||||
secretId: secret.id,
|
|
||||||
secretKey: secret.secretKey,
|
|
||||||
secretVersion: secret.version
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if ((changes.secrets?.delete?.length ?? 0) > 0) {
|
|
||||||
const deletedSecrets = await secretV2BridgeService.deleteManySecret({
|
|
||||||
secretPath,
|
|
||||||
environment,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
secrets: changes.secrets?.delete ?? [],
|
|
||||||
tx: trx,
|
|
||||||
commitChanges
|
|
||||||
});
|
|
||||||
secretMutationEvents.push({
|
|
||||||
type: EventType.DELETE_SECRETS,
|
|
||||||
metadata: {
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
secrets: deletedSecrets.map((secret) => ({
|
|
||||||
secretId: secret.id,
|
|
||||||
secretKey: secret.secretKey,
|
|
||||||
secretVersion: secret.version
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (commitChanges?.length > 0) {
|
|
||||||
const commit = await folderCommitService.createCommit(
|
|
||||||
{
|
|
||||||
actor: {
|
|
||||||
type: actor || ActorType.PLATFORM,
|
|
||||||
metadata: {
|
|
||||||
id: actorId
|
|
||||||
}
|
|
||||||
},
|
|
||||||
message,
|
|
||||||
folderId: targetFolder.id,
|
|
||||||
changes: commitChanges
|
|
||||||
},
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
folderChanges,
|
|
||||||
commitId: commit?.id,
|
|
||||||
secretMutationEvents
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
folderChanges,
|
|
||||||
secretMutationEvents
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getCommitsCount,
|
getCommitsCount,
|
||||||
getCommitsForFolder,
|
getCommitsForFolder,
|
||||||
@@ -849,7 +478,6 @@ export const pitServiceFactory = ({
|
|||||||
compareCommitChanges,
|
compareCommitChanges,
|
||||||
rollbackToCommit,
|
rollbackToCommit,
|
||||||
revertCommit,
|
revertCommit,
|
||||||
getFolderStateAtCommit,
|
getFolderStateAtCommit
|
||||||
processNewCommitRaw
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import { ProjectType } from "@app/db/schemas";
|
|
||||||
import {
|
import {
|
||||||
InfisicalProjectTemplate,
|
InfisicalProjectTemplate,
|
||||||
TUnpackedPermission
|
TUnpackedPermission
|
||||||
@@ -7,21 +6,18 @@ import { getPredefinedRoles } from "@app/services/project-role/project-role-fns"
|
|||||||
|
|
||||||
import { ProjectTemplateDefaultEnvironments } from "./project-template-constants";
|
import { ProjectTemplateDefaultEnvironments } from "./project-template-constants";
|
||||||
|
|
||||||
export const getDefaultProjectTemplate = (orgId: string, type: ProjectType) => ({
|
export const getDefaultProjectTemplate = (orgId: string) => ({
|
||||||
id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // random ID to appease zod
|
id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // random ID to appease zod
|
||||||
type,
|
|
||||||
name: InfisicalProjectTemplate.Default,
|
name: InfisicalProjectTemplate.Default,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
description: `Infisical's ${type} default project template`,
|
description: `Infisical's default project template`,
|
||||||
environments: type === ProjectType.SecretManager ? ProjectTemplateDefaultEnvironments : null,
|
environments: ProjectTemplateDefaultEnvironments,
|
||||||
roles: [...getPredefinedRoles({ projectId: "project-template", projectType: type })].map(
|
roles: getPredefinedRoles({ projectId: "project-template" }) as Array<{
|
||||||
({ name, slug, permissions }) => ({
|
name: string;
|
||||||
name,
|
slug: string;
|
||||||
slug,
|
permissions: TUnpackedPermission[];
|
||||||
permissions: permissions as TUnpackedPermission[]
|
}>,
|
||||||
})
|
|
||||||
),
|
|
||||||
orgId
|
orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { packRules } from "@casl/ability/extra";
|
import { packRules } from "@casl/ability/extra";
|
||||||
|
|
||||||
import { ProjectType, TProjectTemplates } from "@app/db/schemas";
|
import { TProjectTemplates } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
@@ -29,13 +29,11 @@ const $unpackProjectTemplate = ({ roles, environments, ...rest }: TProjectTempla
|
|||||||
...rest,
|
...rest,
|
||||||
environments: environments as TProjectTemplateEnvironment[],
|
environments: environments as TProjectTemplateEnvironment[],
|
||||||
roles: [
|
roles: [
|
||||||
...getPredefinedRoles({ projectId: "project-template", projectType: rest.type as ProjectType }).map(
|
...getPredefinedRoles({ projectId: "project-template" }).map(({ name, slug, permissions }) => ({
|
||||||
({ name, slug, permissions }) => ({
|
name,
|
||||||
name,
|
slug,
|
||||||
slug,
|
permissions: permissions as TUnpackedPermission[]
|
||||||
permissions: permissions as TUnpackedPermission[]
|
})),
|
||||||
})
|
|
||||||
),
|
|
||||||
...(roles as TProjectTemplateRole[]).map((role) => ({
|
...(roles as TProjectTemplateRole[]).map((role) => ({
|
||||||
...role,
|
...role,
|
||||||
permissions: unpackPermissions(role.permissions)
|
permissions: unpackPermissions(role.permissions)
|
||||||
@@ -48,10 +46,7 @@ export const projectTemplateServiceFactory = ({
|
|||||||
permissionService,
|
permissionService,
|
||||||
projectTemplateDAL
|
projectTemplateDAL
|
||||||
}: TProjectTemplatesServiceFactoryDep): TProjectTemplateServiceFactory => {
|
}: TProjectTemplatesServiceFactoryDep): TProjectTemplateServiceFactory => {
|
||||||
const listProjectTemplatesByOrg: TProjectTemplateServiceFactory["listProjectTemplatesByOrg"] = async (
|
const listProjectTemplatesByOrg: TProjectTemplateServiceFactory["listProjectTemplatesByOrg"] = async (actor) => {
|
||||||
actor,
|
|
||||||
type
|
|
||||||
) => {
|
|
||||||
const plan = await licenseService.getPlan(actor.orgId);
|
const plan = await licenseService.getPlan(actor.orgId);
|
||||||
|
|
||||||
if (!plan.projectTemplates)
|
if (!plan.projectTemplates)
|
||||||
@@ -70,14 +65,11 @@ export const projectTemplateServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
|
||||||
|
|
||||||
const projectTemplates = await projectTemplateDAL.find({
|
const projectTemplates = await projectTemplateDAL.find({
|
||||||
orgId: actor.orgId,
|
orgId: actor.orgId
|
||||||
...(type ? { type } : {})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...(type
|
getDefaultProjectTemplate(actor.orgId),
|
||||||
? [getDefaultProjectTemplate(actor.orgId, type)]
|
|
||||||
: Object.values(ProjectType).map((projectType) => getDefaultProjectTemplate(actor.orgId, projectType))),
|
|
||||||
...projectTemplates.map((template) => $unpackProjectTemplate(template))
|
...projectTemplates.map((template) => $unpackProjectTemplate(template))
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
@@ -142,7 +134,7 @@ export const projectTemplateServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createProjectTemplate: TProjectTemplateServiceFactory["createProjectTemplate"] = async (
|
const createProjectTemplate: TProjectTemplateServiceFactory["createProjectTemplate"] = async (
|
||||||
{ roles, environments, type, ...params },
|
{ roles, environments, ...params },
|
||||||
actor
|
actor
|
||||||
) => {
|
) => {
|
||||||
const plan = await licenseService.getPlan(actor.orgId);
|
const plan = await licenseService.getPlan(actor.orgId);
|
||||||
@@ -162,10 +154,6 @@ export const projectTemplateServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates);
|
||||||
|
|
||||||
if (environments && type !== ProjectType.SecretManager) {
|
|
||||||
throw new BadRequestError({ message: "Cannot configure environments for non-SecretManager project templates" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) {
|
if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
@@ -188,10 +176,8 @@ export const projectTemplateServiceFactory = ({
|
|||||||
const projectTemplate = await projectTemplateDAL.create({
|
const projectTemplate = await projectTemplateDAL.create({
|
||||||
...params,
|
...params,
|
||||||
roles: JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) }))),
|
roles: JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) }))),
|
||||||
environments:
|
environments: environments ? JSON.stringify(environments ?? ProjectTemplateDefaultEnvironments) : null,
|
||||||
type === ProjectType.SecretManager ? JSON.stringify(environments ?? ProjectTemplateDefaultEnvironments) : null,
|
orgId: actor.orgId
|
||||||
orgId: actor.orgId,
|
|
||||||
type
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return $unpackProjectTemplate(projectTemplate);
|
return $unpackProjectTemplate(projectTemplate);
|
||||||
@@ -222,11 +208,6 @@ export const projectTemplateServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
|
||||||
if (projectTemplate.type !== ProjectType.SecretManager && environments)
|
|
||||||
throw new BadRequestError({ message: "Cannot configure environments for non-SecretManager project templates" });
|
|
||||||
|
|
||||||
if (projectTemplate.type === ProjectType.SecretManager && environments === null)
|
|
||||||
throw new BadRequestError({ message: "Environments cannot be removed for SecretManager project templates" });
|
|
||||||
|
|
||||||
if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) {
|
if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectMembershipRole, ProjectType, TProjectEnvironments } from "@app/db/schemas";
|
import { ProjectMembershipRole, TProjectEnvironments } from "@app/db/schemas";
|
||||||
import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
|
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
|
||||||
@@ -15,7 +15,6 @@ export type TProjectTemplateRole = {
|
|||||||
|
|
||||||
export type TCreateProjectTemplateDTO = {
|
export type TCreateProjectTemplateDTO = {
|
||||||
name: string;
|
name: string;
|
||||||
type: ProjectType;
|
|
||||||
description?: string;
|
description?: string;
|
||||||
roles: TProjectTemplateRole[];
|
roles: TProjectTemplateRole[];
|
||||||
environments?: TProjectTemplateEnvironment[] | null;
|
environments?: TProjectTemplateEnvironment[] | null;
|
||||||
@@ -30,15 +29,11 @@ export enum InfisicalProjectTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type TProjectTemplateServiceFactory = {
|
export type TProjectTemplateServiceFactory = {
|
||||||
listProjectTemplatesByOrg: (
|
listProjectTemplatesByOrg: (actor: OrgServiceActor) => Promise<
|
||||||
actor: OrgServiceActor,
|
|
||||||
type?: ProjectType
|
|
||||||
) => Promise<
|
|
||||||
(
|
(
|
||||||
| {
|
| {
|
||||||
id: string;
|
id: string;
|
||||||
name: InfisicalProjectTemplate;
|
name: InfisicalProjectTemplate;
|
||||||
type: string;
|
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
description: string;
|
description: string;
|
||||||
@@ -63,7 +58,6 @@ export type TProjectTemplateServiceFactory = {
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
environments: TProjectTemplateEnvironment[];
|
environments: TProjectTemplateEnvironment[];
|
||||||
type: string;
|
|
||||||
roles: {
|
roles: {
|
||||||
permissions: {
|
permissions: {
|
||||||
action: string[];
|
action: string[];
|
||||||
@@ -100,7 +94,6 @@ export type TProjectTemplateServiceFactory = {
|
|||||||
}[];
|
}[];
|
||||||
name: string;
|
name: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
type: string;
|
|
||||||
id: string;
|
id: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
@@ -125,7 +118,6 @@ export type TProjectTemplateServiceFactory = {
|
|||||||
name: string;
|
name: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
description?: string | null | undefined;
|
description?: string | null | undefined;
|
||||||
@@ -148,7 +140,6 @@ export type TProjectTemplateServiceFactory = {
|
|||||||
name: string;
|
name: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
description?: string | null | undefined;
|
description?: string | null | undefined;
|
||||||
@@ -171,7 +162,6 @@ export type TProjectTemplateServiceFactory = {
|
|||||||
}[];
|
}[];
|
||||||
name: string;
|
name: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
type: string;
|
|
||||||
id: string;
|
id: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
@@ -194,7 +184,6 @@ export type TProjectTemplateServiceFactory = {
|
|||||||
name: string;
|
name: string;
|
||||||
}[];
|
}[];
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
|
||||||
orgId: string;
|
orgId: string;
|
||||||
id: string;
|
id: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
||||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||||
|
|
||||||
import { ActionProjectType, TableName } from "@app/db/schemas";
|
import { TableName } from "@app/db/schemas";
|
||||||
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
||||||
import { ms } from "@app/lib/ms";
|
import { ms } from "@app/lib/ms";
|
||||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||||
@@ -61,8 +61,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
||||||
const { permission: targetUserPermission, membership } = await permissionService.getProjectPermission({
|
const { permission: targetUserPermission, membership } = await permissionService.getProjectPermission({
|
||||||
@@ -70,8 +69,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorId: projectMembership.userId,
|
actorId: projectMembership.userId,
|
||||||
projectId: projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
@@ -166,8 +164,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
||||||
const { permission: targetUserPermission } = await permissionService.getProjectPermission({
|
const { permission: targetUserPermission } = await permissionService.getProjectPermission({
|
||||||
@@ -175,8 +172,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorId: projectMembership.userId,
|
actorId: projectMembership.userId,
|
||||||
projectId: projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
@@ -276,8 +272,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
@@ -322,8 +317,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
@@ -349,8 +343,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.Any
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
@@ -579,9 +579,6 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const serverCfg = await getServerCfg();
|
const serverCfg = await getServerCfg();
|
||||||
const hasEmailChanged = email?.toLowerCase() !== membership.email;
|
|
||||||
const defaultEmailVerified =
|
|
||||||
org.orgAuthMethod === OrgAuthMethod.OIDC ? serverCfg.trustOidcEmails : serverCfg.trustSamlEmails;
|
|
||||||
await userDAL.transaction(async (tx) => {
|
await userDAL.transaction(async (tx) => {
|
||||||
await userAliasDAL.update(
|
await userAliasDAL.update(
|
||||||
{
|
{
|
||||||
@@ -608,7 +605,8 @@ export const scimServiceFactory = ({
|
|||||||
firstName,
|
firstName,
|
||||||
email: email?.toLowerCase(),
|
email: email?.toLowerCase(),
|
||||||
lastName,
|
lastName,
|
||||||
isEmailVerified: hasEmailChanged ? defaultEmailVerified : undefined
|
isEmailVerified:
|
||||||
|
org.orgAuthMethod === OrgAuthMethod.OIDC ? serverCfg.trustOidcEmails : serverCfg.trustSamlEmails
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
@@ -23,7 +23,6 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>,
|
filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>,
|
||||||
customFilter?: {
|
customFilter?: {
|
||||||
sapId?: string;
|
sapId?: string;
|
||||||
envId?: string;
|
|
||||||
}
|
}
|
||||||
) =>
|
) =>
|
||||||
tx(TableName.SecretApprovalPolicy)
|
tx(TableName.SecretApprovalPolicy)
|
||||||
@@ -34,17 +33,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
void qb.where(`${TableName.SecretApprovalPolicy}.id`, "=", customFilter.sapId);
|
void qb.where(`${TableName.SecretApprovalPolicy}.id`, "=", customFilter.sapId);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.join(
|
.join(TableName.Environment, `${TableName.SecretApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
||||||
TableName.SecretApprovalPolicyEnvironment,
|
|
||||||
`${TableName.SecretApprovalPolicyEnvironment}.policyId`,
|
|
||||||
`${TableName.SecretApprovalPolicy}.id`
|
|
||||||
)
|
|
||||||
.join(TableName.Environment, `${TableName.SecretApprovalPolicyEnvironment}.envId`, `${TableName.Environment}.id`)
|
|
||||||
.where((qb) => {
|
|
||||||
if (customFilter?.envId) {
|
|
||||||
void qb.where(`${TableName.SecretApprovalPolicyEnvironment}.envId`, "=", customFilter.envId);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.SecretApprovalPolicyApprover,
|
TableName.SecretApprovalPolicyApprover,
|
||||||
`${TableName.SecretApprovalPolicy}.id`,
|
`${TableName.SecretApprovalPolicy}.id`,
|
||||||
@@ -108,7 +97,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
.select(
|
.select(
|
||||||
tx.ref("name").withSchema(TableName.Environment).as("envName"),
|
tx.ref("name").withSchema(TableName.Environment).as("envName"),
|
||||||
tx.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
tx.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
||||||
tx.ref("id").withSchema(TableName.Environment).as("environmentId"),
|
tx.ref("id").withSchema(TableName.Environment).as("envId"),
|
||||||
tx.ref("projectId").withSchema(TableName.Environment)
|
tx.ref("projectId").withSchema(TableName.Environment)
|
||||||
)
|
)
|
||||||
.select(selectAllTableCols(TableName.SecretApprovalPolicy))
|
.select(selectAllTableCols(TableName.SecretApprovalPolicy))
|
||||||
@@ -157,15 +146,6 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
firstName,
|
firstName,
|
||||||
lastName
|
lastName
|
||||||
})
|
})
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "environmentId",
|
|
||||||
label: "environments" as const,
|
|
||||||
mapper: ({ environmentId, envName, envSlug }) => ({
|
|
||||||
id: environmentId,
|
|
||||||
name: envName,
|
|
||||||
slug: envSlug
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -180,7 +160,6 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>,
|
filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>,
|
||||||
customFilter?: {
|
customFilter?: {
|
||||||
sapId?: string;
|
sapId?: string;
|
||||||
envId?: string;
|
|
||||||
},
|
},
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
) => {
|
) => {
|
||||||
@@ -242,15 +221,6 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
mapper: ({ approverGroupUserId: userId }) => ({
|
mapper: ({ approverGroupUserId: userId }) => ({
|
||||||
userId
|
userId
|
||||||
})
|
})
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "environmentId",
|
|
||||||
label: "environments" as const,
|
|
||||||
mapper: ({ environmentId, envName, envSlug }) => ({
|
|
||||||
id: environmentId,
|
|
||||||
name: envName,
|
|
||||||
slug: envSlug
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -265,74 +235,5 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
return softDeletedPolicy;
|
return softDeletedPolicy;
|
||||||
};
|
};
|
||||||
|
|
||||||
const findPolicyByEnvIdAndSecretPath = async (
|
return { ...secretApprovalPolicyOrm, findById, find, softDeleteById };
|
||||||
{ envIds, secretPath }: { envIds: string[]; secretPath: string },
|
|
||||||
tx?: Knex
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const docs = await (tx || db.replicaNode())(TableName.SecretApprovalPolicy)
|
|
||||||
.join(
|
|
||||||
TableName.SecretApprovalPolicyEnvironment,
|
|
||||||
`${TableName.SecretApprovalPolicyEnvironment}.policyId`,
|
|
||||||
`${TableName.SecretApprovalPolicy}.id`
|
|
||||||
)
|
|
||||||
.join(
|
|
||||||
TableName.Environment,
|
|
||||||
`${TableName.SecretApprovalPolicyEnvironment}.envId`,
|
|
||||||
`${TableName.Environment}.id`
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
buildFindFilter(
|
|
||||||
{
|
|
||||||
$in: {
|
|
||||||
envId: envIds
|
|
||||||
}
|
|
||||||
},
|
|
||||||
TableName.SecretApprovalPolicyEnvironment
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
buildFindFilter(
|
|
||||||
{
|
|
||||||
secretPath
|
|
||||||
},
|
|
||||||
TableName.SecretApprovalPolicy
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.whereNull(`${TableName.SecretApprovalPolicy}.deletedAt`)
|
|
||||||
.orderBy("deletedAt", "desc")
|
|
||||||
.orderByRaw(`"deletedAt" IS NULL`)
|
|
||||||
.select(selectAllTableCols(TableName.SecretApprovalPolicy))
|
|
||||||
.select(db.ref("name").withSchema(TableName.Environment).as("envName"))
|
|
||||||
.select(db.ref("slug").withSchema(TableName.Environment).as("envSlug"))
|
|
||||||
.select(db.ref("id").withSchema(TableName.Environment).as("environmentId"))
|
|
||||||
.select(db.ref("projectId").withSchema(TableName.Environment));
|
|
||||||
const formattedDocs = sqlNestRelationships({
|
|
||||||
data: docs,
|
|
||||||
key: "id",
|
|
||||||
parentMapper: (data) => ({
|
|
||||||
projectId: data.projectId,
|
|
||||||
...SecretApprovalPoliciesSchema.parse(data)
|
|
||||||
}),
|
|
||||||
childrenMapper: [
|
|
||||||
{
|
|
||||||
key: "environmentId",
|
|
||||||
label: "environments" as const,
|
|
||||||
mapper: ({ environmentId: id, envName, envSlug }) => ({
|
|
||||||
id,
|
|
||||||
name: envName,
|
|
||||||
slug: envSlug
|
|
||||||
})
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
return formattedDocs?.[0];
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({ error, name: "findPolicyByEnvIdAndSecretPath" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return { ...secretApprovalPolicyOrm, findById, find, softDeleteById, findPolicyByEnvIdAndSecretPath };
|
|
||||||
};
|
};
|
||||||
|
@@ -1,32 +0,0 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
|
||||||
import { TableName } from "@app/db/schemas";
|
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
|
||||||
import { buildFindFilter, ormify, selectAllTableCols } from "@app/lib/knex";
|
|
||||||
|
|
||||||
export type TSecretApprovalPolicyEnvironmentDALFactory = ReturnType<typeof secretApprovalPolicyEnvironmentDALFactory>;
|
|
||||||
|
|
||||||
export const secretApprovalPolicyEnvironmentDALFactory = (db: TDbClient) => {
|
|
||||||
const secretApprovalPolicyEnvironmentOrm = ormify(db, TableName.SecretApprovalPolicyEnvironment);
|
|
||||||
|
|
||||||
const findAvailablePoliciesByEnvId = async (envId: string, tx?: Knex) => {
|
|
||||||
try {
|
|
||||||
const docs = await (tx || db.replicaNode())(TableName.SecretApprovalPolicyEnvironment)
|
|
||||||
.join(
|
|
||||||
TableName.SecretApprovalPolicy,
|
|
||||||
`${TableName.SecretApprovalPolicyEnvironment}.policyId`,
|
|
||||||
`${TableName.SecretApprovalPolicy}.id`
|
|
||||||
)
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
.where(buildFindFilter({ envId }, TableName.SecretApprovalPolicyEnvironment))
|
|
||||||
.whereNull(`${TableName.SecretApprovalPolicy}.deletedAt`)
|
|
||||||
.select(selectAllTableCols(TableName.SecretApprovalPolicyEnvironment));
|
|
||||||
return docs;
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({ error, name: "findAvailablePoliciesByEnvId" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return { ...secretApprovalPolicyEnvironmentOrm, findAvailablePoliciesByEnvId };
|
|
||||||
};
|
|
@@ -1,7 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import picomatch from "picomatch";
|
import picomatch from "picomatch";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@@ -19,7 +18,6 @@ import {
|
|||||||
TSecretApprovalPolicyBypasserDALFactory
|
TSecretApprovalPolicyBypasserDALFactory
|
||||||
} from "./secret-approval-policy-approver-dal";
|
} from "./secret-approval-policy-approver-dal";
|
||||||
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
||||||
import { TSecretApprovalPolicyEnvironmentDALFactory } from "./secret-approval-policy-environment-dal";
|
|
||||||
import {
|
import {
|
||||||
TCreateSapDTO,
|
TCreateSapDTO,
|
||||||
TDeleteSapDTO,
|
TDeleteSapDTO,
|
||||||
@@ -37,13 +35,12 @@ const getPolicyScore = (policy: { secretPath?: string | null }) =>
|
|||||||
type TSecretApprovalPolicyServiceFactoryDep = {
|
type TSecretApprovalPolicyServiceFactoryDep = {
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory;
|
secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory;
|
||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne" | "find">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||||
userDAL: Pick<TUserDALFactory, "find">;
|
userDAL: Pick<TUserDALFactory, "find">;
|
||||||
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
||||||
secretApprovalPolicyBypasserDAL: TSecretApprovalPolicyBypasserDALFactory;
|
secretApprovalPolicyBypasserDAL: TSecretApprovalPolicyBypasserDALFactory;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "update">;
|
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "update">;
|
||||||
secretApprovalPolicyEnvironmentDAL: TSecretApprovalPolicyEnvironmentDALFactory;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
|
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
|
||||||
@@ -53,30 +50,27 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
permissionService,
|
permissionService,
|
||||||
secretApprovalPolicyApproverDAL,
|
secretApprovalPolicyApproverDAL,
|
||||||
secretApprovalPolicyBypasserDAL,
|
secretApprovalPolicyBypasserDAL,
|
||||||
secretApprovalPolicyEnvironmentDAL,
|
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
userDAL,
|
userDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
secretApprovalRequestDAL
|
secretApprovalRequestDAL
|
||||||
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
||||||
const $policyExists = async ({
|
const $policyExists = async ({
|
||||||
envIds,
|
|
||||||
envId,
|
envId,
|
||||||
secretPath,
|
secretPath,
|
||||||
policyId
|
policyId
|
||||||
}: {
|
}: {
|
||||||
envIds?: string[];
|
envId: string;
|
||||||
envId?: string;
|
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
policyId?: string;
|
policyId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
if (!envIds && !envId) {
|
const policy = await secretApprovalPolicyDAL
|
||||||
throw new BadRequestError({ message: "At least one environment should be provided" });
|
.findOne({
|
||||||
}
|
envId,
|
||||||
const policy = await secretApprovalPolicyDAL.findPolicyByEnvIdAndSecretPath({
|
secretPath,
|
||||||
envIds: envId ? [envId] : envIds || [],
|
deletedAt: null
|
||||||
secretPath
|
})
|
||||||
});
|
.catch(() => null);
|
||||||
|
|
||||||
return policyId ? policy && policy.id !== policyId : Boolean(policy);
|
return policyId ? policy && policy.id !== policyId : Boolean(policy);
|
||||||
};
|
};
|
||||||
@@ -93,7 +87,6 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
projectId,
|
projectId,
|
||||||
secretPath,
|
secretPath,
|
||||||
environment,
|
environment,
|
||||||
environments,
|
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals
|
allowedSelfApprovals
|
||||||
}: TCreateSapDTO) => {
|
}: TCreateSapDTO) => {
|
||||||
@@ -117,8 +110,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
@@ -133,23 +125,17 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const mergedEnvs = (environment ? [environment] : environments) || [];
|
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
||||||
if (mergedEnvs.length === 0) {
|
if (!env) {
|
||||||
throw new BadRequestError({ message: "Must provide either environment or environments" });
|
throw new NotFoundError({
|
||||||
}
|
message: `Environment with slug '${environment}' not found in project with ID ${projectId}`
|
||||||
const envs = await projectEnvDAL.find({ $in: { slug: mergedEnvs }, projectId });
|
});
|
||||||
if (!envs.length || envs.length !== mergedEnvs.length) {
|
|
||||||
const notFoundEnvs = mergedEnvs.filter((env) => !envs.find((el) => el.slug === env));
|
|
||||||
throw new NotFoundError({ message: `One or more environments not found: ${notFoundEnvs.join(", ")}` });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const env of envs) {
|
if (await $policyExists({ envId: env.id, secretPath })) {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
throw new BadRequestError({
|
||||||
if (await $policyExists({ envId: env.id, secretPath })) {
|
message: `A policy for secret path '${secretPath}' already exists in environment '${environment}'`
|
||||||
throw new BadRequestError({
|
});
|
||||||
message: `A policy for secret path '${secretPath}' already exists in environment '${env.slug}'`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let groupBypassers: string[] = [];
|
let groupBypassers: string[] = [];
|
||||||
@@ -193,7 +179,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
const doc = await secretApprovalPolicyDAL.create(
|
const doc = await secretApprovalPolicyDAL.create(
|
||||||
{
|
{
|
||||||
envId: envs[0].id,
|
envId: env.id,
|
||||||
approvals,
|
approvals,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
@@ -202,13 +188,6 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
await secretApprovalPolicyEnvironmentDAL.insertMany(
|
|
||||||
envs.map((env) => ({
|
|
||||||
envId: env.id,
|
|
||||||
policyId: doc.id
|
|
||||||
})),
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
let userApproverIds = userApprovers;
|
let userApproverIds = userApprovers;
|
||||||
if (userApproverNames.length) {
|
if (userApproverNames.length) {
|
||||||
@@ -272,13 +251,12 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
return doc;
|
return doc;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { ...secretApproval, environments: envs, projectId, environment: envs[0] };
|
return { ...secretApproval, environment: env, projectId };
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateSecretApprovalPolicy = async ({
|
const updateSecretApprovalPolicy = async ({
|
||||||
approvers,
|
approvers,
|
||||||
bypassers,
|
bypassers,
|
||||||
environments,
|
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -308,26 +286,17 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
message: `Secret approval policy with ID '${secretPolicyId}' not found`
|
message: `Secret approval policy with ID '${secretPolicyId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let envs = secretApprovalPolicy.environments;
|
|
||||||
if (
|
if (
|
||||||
environments &&
|
await $policyExists({
|
||||||
(environments.length !== envs.length || environments.some((env) => !envs.find((el) => el.slug === env)))
|
envId: secretApprovalPolicy.envId,
|
||||||
|
secretPath: secretPath || secretApprovalPolicy.secretPath,
|
||||||
|
policyId: secretApprovalPolicy.id
|
||||||
|
})
|
||||||
) {
|
) {
|
||||||
envs = await projectEnvDAL.find({ $in: { slug: environments }, projectId: secretApprovalPolicy.projectId });
|
throw new BadRequestError({
|
||||||
}
|
message: `A policy for secret path '${secretPath}' already exists in environment '${secretApprovalPolicy.environment.slug}'`
|
||||||
for (const env of envs) {
|
});
|
||||||
if (
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await $policyExists({
|
|
||||||
envId: env.id,
|
|
||||||
secretPath: secretPath || secretApprovalPolicy.secretPath,
|
|
||||||
policyId: secretApprovalPolicy.id
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `A policy for secret path '${secretPath || secretApprovalPolicy.secretPath}' already exists in environment '${env.slug}'`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
@@ -335,8 +304,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: secretApprovalPolicy.projectId,
|
projectId: secretApprovalPolicy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
@@ -444,17 +412,6 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (environments) {
|
|
||||||
await secretApprovalPolicyEnvironmentDAL.delete({ policyId: doc.id }, tx);
|
|
||||||
await secretApprovalPolicyEnvironmentDAL.insertMany(
|
|
||||||
envs.map((env) => ({
|
|
||||||
envId: env.id,
|
|
||||||
policyId: doc.id
|
|
||||||
})),
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await secretApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx);
|
await secretApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx);
|
||||||
|
|
||||||
if (bypasserUserIds.length) {
|
if (bypasserUserIds.length) {
|
||||||
@@ -481,8 +438,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
...updatedSap,
|
...updatedSap,
|
||||||
environments: secretApprovalPolicy.environments,
|
environment: secretApprovalPolicy.environment,
|
||||||
environment: secretApprovalPolicy.environments[0],
|
|
||||||
projectId: secretApprovalPolicy.projectId
|
projectId: secretApprovalPolicy.projectId
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -503,8 +459,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: sapPolicy.projectId,
|
projectId: sapPolicy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
@@ -528,12 +483,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
const updatedPolicy = await secretApprovalPolicyDAL.softDeleteById(secretPolicyId, tx);
|
const updatedPolicy = await secretApprovalPolicyDAL.softDeleteById(secretPolicyId, tx);
|
||||||
return updatedPolicy;
|
return updatedPolicy;
|
||||||
});
|
});
|
||||||
return {
|
return { ...deletedPolicy, projectId: sapPolicy.projectId, environment: sapPolicy.environment };
|
||||||
...deletedPolicy,
|
|
||||||
projectId: sapPolicy.projectId,
|
|
||||||
environments: sapPolicy.environments,
|
|
||||||
environment: sapPolicy.environments[0]
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSecretApprovalPolicyByProjectId = async ({
|
const getSecretApprovalPolicyByProjectId = async ({
|
||||||
@@ -548,8 +498,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
@@ -566,7 +515,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const policies = await secretApprovalPolicyDAL.find({ deletedAt: null }, { envId: env.id });
|
const policies = await secretApprovalPolicyDAL.find({ envId: env.id, deletedAt: null });
|
||||||
if (!policies.length) return;
|
if (!policies.length) return;
|
||||||
// this will filter policies either without scoped to secret path or the one that matches with secret path
|
// this will filter policies either without scoped to secret path or the one that matches with secret path
|
||||||
const policiesFilteredByPath = policies.filter(
|
const policiesFilteredByPath = policies.filter(
|
||||||
@@ -593,8 +542,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return getSecretApprovalPolicy(projectId, environment, secretPath);
|
return getSecretApprovalPolicy(projectId, environment, secretPath);
|
||||||
@@ -620,8 +568,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: sapPolicy.projectId,
|
projectId: sapPolicy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
|
@@ -5,8 +5,7 @@ import { ApproverType, BypasserType } from "../access-approval-policy/access-app
|
|||||||
export type TCreateSapDTO = {
|
export type TCreateSapDTO = {
|
||||||
approvals: number;
|
approvals: number;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
environment?: string;
|
environment: string;
|
||||||
environments?: string[];
|
|
||||||
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
|
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
|
||||||
bypassers?: (
|
bypassers?: (
|
||||||
| { type: BypasserType.Group; id: string }
|
| { type: BypasserType.Group; id: string }
|
||||||
@@ -30,7 +29,6 @@ export type TUpdateSapDTO = {
|
|||||||
name?: string;
|
name?: string;
|
||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
allowedSelfApprovals?: boolean;
|
allowedSelfApprovals?: boolean;
|
||||||
environments?: string[];
|
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TDeleteSapDTO = {
|
export type TDeleteSapDTO = {
|
||||||
|
@@ -40,13 +40,6 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SecretApprovalRequest}.policyId`,
|
`${TableName.SecretApprovalRequest}.policyId`,
|
||||||
`${TableName.SecretApprovalPolicy}.id`
|
`${TableName.SecretApprovalPolicy}.id`
|
||||||
)
|
)
|
||||||
.leftJoin(TableName.SecretApprovalPolicyEnvironment, (bd) => {
|
|
||||||
bd.on(
|
|
||||||
`${TableName.SecretApprovalPolicy}.id`,
|
|
||||||
"=",
|
|
||||||
`${TableName.SecretApprovalPolicyEnvironment}.policyId`
|
|
||||||
).andOn(`${TableName.SecretApprovalPolicyEnvironment}.envId`, "=", `${TableName.SecretFolder}.envId`);
|
|
||||||
})
|
|
||||||
.leftJoin<TUsers>(
|
.leftJoin<TUsers>(
|
||||||
db(TableName.Users).as("statusChangedByUser"),
|
db(TableName.Users).as("statusChangedByUser"),
|
||||||
`${TableName.SecretApprovalRequest}.statusChangedByUserId`,
|
`${TableName.SecretApprovalRequest}.statusChangedByUserId`,
|
||||||
@@ -153,7 +146,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
tx.ref("projectId").withSchema(TableName.Environment),
|
tx.ref("projectId").withSchema(TableName.Environment),
|
||||||
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
|
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
|
||||||
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
||||||
tx.ref("envId").withSchema(TableName.SecretApprovalPolicyEnvironment).as("policyEnvId"),
|
tx.ref("envId").withSchema(TableName.SecretApprovalPolicy).as("policyEnvId"),
|
||||||
tx.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
tx.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
||||||
tx.ref("allowedSelfApprovals").withSchema(TableName.SecretApprovalPolicy).as("policyAllowedSelfApprovals"),
|
tx.ref("allowedSelfApprovals").withSchema(TableName.SecretApprovalPolicy).as("policyAllowedSelfApprovals"),
|
||||||
tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
||||||
|
@@ -36,7 +36,7 @@ export const sendApprovalEmailsFn = async ({
|
|||||||
firstName: reviewerUser.firstName,
|
firstName: reviewerUser.firstName,
|
||||||
projectName: project.name,
|
projectName: project.name,
|
||||||
organizationName: project.organization.name,
|
organizationName: project.organization.name,
|
||||||
approvalUrl: `${cfg.SITE_URL}/projects/secret-management/${project.id}/approval?requestId=${secretApprovalRequest.id}`
|
approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval?requestId=${secretApprovalRequest.id}`
|
||||||
},
|
},
|
||||||
template: SmtpTemplates.SecretApprovalRequestNeedsReview
|
template: SmtpTemplates.SecretApprovalRequestNeedsReview
|
||||||
});
|
});
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
/* eslint-disable no-nested-ternary */
|
/* eslint-disable no-nested-ternary */
|
||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ActionProjectType,
|
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
SecretEncryptionAlgo,
|
SecretEncryptionAlgo,
|
||||||
SecretKeyEncoding,
|
SecretKeyEncoding,
|
||||||
@@ -65,14 +63,10 @@ import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
|||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import {
|
import { throwIfMissingSecretReadValueOrDescribePermission } from "../permission/permission-fns";
|
||||||
hasSecretReadValueOrDescribePermission,
|
|
||||||
throwIfMissingSecretReadValueOrDescribePermission
|
|
||||||
} from "../permission/permission-fns";
|
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||||
import { ProjectPermissionSecretActions, ProjectPermissionSub } from "../permission/project-permission";
|
import { ProjectPermissionSecretActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
|
import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
|
||||||
import { scanSecretPolicyViolations } from "../secret-scanning-v2/secret-scanning-v2-fns";
|
|
||||||
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
||||||
import { TSecretApprovalRequestDALFactory } from "./secret-approval-request-dal";
|
import { TSecretApprovalRequestDALFactory } from "./secret-approval-request-dal";
|
||||||
import { sendApprovalEmailsFn } from "./secret-approval-request-fns";
|
import { sendApprovalEmailsFn } from "./secret-approval-request-fns";
|
||||||
@@ -189,8 +183,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId, policyId);
|
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId, policyId);
|
||||||
@@ -217,8 +210,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
@@ -270,8 +262,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
@@ -280,19 +271,13 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
) {
|
) {
|
||||||
throw new ForbiddenRequestError({ message: "User has insufficient privileges" });
|
throw new ForbiddenRequestError({ message: "User has insufficient privileges" });
|
||||||
}
|
}
|
||||||
const getHasSecretReadAccess = (environment: string, tags: { slug: string }[], secretPath?: string) => {
|
|
||||||
const canRead = hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
|
const hasSecretReadAccess = permission.can(
|
||||||
environment,
|
ProjectPermissionSecretActions.DescribeAndReadValue,
|
||||||
secretPath: secretPath || "/",
|
ProjectPermissionSub.Secrets
|
||||||
secretTags: tags.map((i) => i.slug)
|
);
|
||||||
});
|
|
||||||
return canRead;
|
|
||||||
};
|
|
||||||
|
|
||||||
let secrets;
|
let secrets;
|
||||||
const secretPath = await folderDAL.findSecretPathByFolderIds(secretApprovalRequest.projectId, [
|
|
||||||
secretApprovalRequest.folderId
|
|
||||||
]);
|
|
||||||
if (shouldUseSecretV2Bridge) {
|
if (shouldUseSecretV2Bridge) {
|
||||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
@@ -308,8 +293,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
version: el.version,
|
version: el.version,
|
||||||
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
|
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
|
||||||
isRotatedSecret: el.secret?.isRotatedSecret ?? false,
|
isRotatedSecret: el.secret?.isRotatedSecret ?? false,
|
||||||
secretValueHidden: !getHasSecretReadAccess(secretApprovalRequest.environment, el.tags, secretPath?.[0]?.path),
|
secretValueHidden: !hasSecretReadAccess,
|
||||||
secretValue: !getHasSecretReadAccess(secretApprovalRequest.environment, el.tags, secretPath?.[0]?.path)
|
secretValue: !hasSecretReadAccess
|
||||||
? INFISICAL_SECRET_VALUE_HIDDEN_MASK
|
? INFISICAL_SECRET_VALUE_HIDDEN_MASK
|
||||||
: el.secret && el.secret.isRotatedSecret
|
: el.secret && el.secret.isRotatedSecret
|
||||||
? undefined
|
? undefined
|
||||||
@@ -324,12 +309,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretKey: el.secret.key,
|
secretKey: el.secret.key,
|
||||||
id: el.secret.id,
|
id: el.secret.id,
|
||||||
version: el.secret.version,
|
version: el.secret.version,
|
||||||
secretValueHidden: !getHasSecretReadAccess(
|
secretValueHidden: !hasSecretReadAccess,
|
||||||
secretApprovalRequest.environment,
|
secretValue: !hasSecretReadAccess
|
||||||
el.tags,
|
|
||||||
secretPath?.[0]?.path
|
|
||||||
),
|
|
||||||
secretValue: !getHasSecretReadAccess(secretApprovalRequest.environment, el.tags, secretPath?.[0]?.path)
|
|
||||||
? INFISICAL_SECRET_VALUE_HIDDEN_MASK
|
? INFISICAL_SECRET_VALUE_HIDDEN_MASK
|
||||||
: el.secret.encryptedValue
|
: el.secret.encryptedValue
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.secret.encryptedValue }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: el.secret.encryptedValue }).toString()
|
||||||
@@ -344,12 +325,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretKey: el.secretVersion.key,
|
secretKey: el.secretVersion.key,
|
||||||
id: el.secretVersion.id,
|
id: el.secretVersion.id,
|
||||||
version: el.secretVersion.version,
|
version: el.secretVersion.version,
|
||||||
secretValueHidden: !getHasSecretReadAccess(
|
secretValueHidden: !hasSecretReadAccess,
|
||||||
secretApprovalRequest.environment,
|
secretValue: !hasSecretReadAccess
|
||||||
el.tags,
|
|
||||||
secretPath?.[0]?.path
|
|
||||||
),
|
|
||||||
secretValue: !getHasSecretReadAccess(secretApprovalRequest.environment, el.tags, secretPath?.[0]?.path)
|
|
||||||
? INFISICAL_SECRET_VALUE_HIDDEN_MASK
|
? INFISICAL_SECRET_VALUE_HIDDEN_MASK
|
||||||
: el.secretVersion.encryptedValue
|
: el.secretVersion.encryptedValue
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedValue }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedValue }).toString()
|
||||||
@@ -367,7 +344,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const encryptedSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
|
const encryptedSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
|
||||||
secrets = encryptedSecrets.map((el) => ({
|
secrets = encryptedSecrets.map((el) => ({
|
||||||
...el,
|
...el,
|
||||||
secretValueHidden: !getHasSecretReadAccess(secretApprovalRequest.environment, el.tags, secretPath?.[0]?.path),
|
secretValueHidden: !hasSecretReadAccess,
|
||||||
...decryptSecretWithBot(el, botKey),
|
...decryptSecretWithBot(el, botKey),
|
||||||
secret: el.secret
|
secret: el.secret
|
||||||
? {
|
? {
|
||||||
@@ -387,6 +364,9 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
: undefined
|
: undefined
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
const secretPath = await folderDAL.findSecretPathByFolderIds(secretApprovalRequest.projectId, [
|
||||||
|
secretApprovalRequest.folderId
|
||||||
|
]);
|
||||||
|
|
||||||
return { ...secretApprovalRequest, secretPath: secretPath?.[0]?.path || "/", commits: secrets };
|
return { ...secretApprovalRequest, secretPath: secretPath?.[0]?.path || "/", commits: secrets };
|
||||||
};
|
};
|
||||||
@@ -431,8 +411,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: secretApprovalRequest.projectId,
|
projectId: secretApprovalRequest.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
@@ -501,8 +480,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: secretApprovalRequest.projectId,
|
projectId: secretApprovalRequest.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
@@ -552,19 +530,13 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
message: "The policy associated with this secret approval request has been deleted."
|
message: "The policy associated with this secret approval request has been deleted."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!policy.envId) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "The policy associated with this secret approval request is not linked to the environment."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { hasRole } = await permissionService.getProjectPermission({
|
const { hasRole } = await permissionService.getProjectPermission({
|
||||||
actor: ActorType.USER,
|
actor: ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -982,7 +954,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
bypassReason,
|
bypassReason,
|
||||||
secretPath: policy.secretPath,
|
secretPath: policy.secretPath,
|
||||||
environment: env.name,
|
environment: env.name,
|
||||||
approvalUrl: `${cfg.SITE_URL}/projects/secret-management/${project.id}/approval`
|
approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`
|
||||||
},
|
},
|
||||||
template: SmtpTemplates.AccessSecretRequestBypassed
|
template: SmtpTemplates.AccessSecretRequestBypassed
|
||||||
});
|
});
|
||||||
@@ -1116,8 +1088,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
|
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
@@ -1397,9 +1368,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
policy,
|
policy,
|
||||||
projectId,
|
projectId,
|
||||||
secretPath,
|
secretPath,
|
||||||
environment,
|
environment
|
||||||
trx: providedTx
|
}: TGenerateSecretApprovalRequestV2BridgeDTO) => {
|
||||||
}: TGenerateSecretApprovalRequestV2BridgeDTO & { trx?: Knex }) => {
|
|
||||||
if (actor === ActorType.SERVICE || actor === ActorType.Machine)
|
if (actor === ActorType.SERVICE || actor === ActorType.Machine)
|
||||||
throw new BadRequestError({ message: "Cannot use service token or machine token over protected branches" });
|
throw new BadRequestError({ message: "Cannot use service token or machine token over protected branches" });
|
||||||
|
|
||||||
@@ -1408,8 +1378,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
@@ -1427,20 +1396,6 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
const project = await projectDAL.findById(projectId);
|
|
||||||
await scanSecretPolicyViolations(
|
|
||||||
projectId,
|
|
||||||
secretPath,
|
|
||||||
[
|
|
||||||
...(data[SecretOperations.Create] || []),
|
|
||||||
...(data[SecretOperations.Update] || []).filter((el) => el.secretValue)
|
|
||||||
].map((el) => ({
|
|
||||||
secretKey: el.secretKey,
|
|
||||||
secretValue: el.secretValue as string
|
|
||||||
})),
|
|
||||||
project.secretDetectionIgnoreValues || []
|
|
||||||
);
|
|
||||||
|
|
||||||
// for created secret approval change
|
// for created secret approval change
|
||||||
const createdSecrets = data[SecretOperations.Create];
|
const createdSecrets = data[SecretOperations.Create];
|
||||||
if (createdSecrets && createdSecrets?.length) {
|
if (createdSecrets && createdSecrets?.length) {
|
||||||
@@ -1640,7 +1595,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const executeApprovalRequestCreation = async (tx: Knex) => {
|
const secretApprovalRequest = await secretApprovalRequestDAL.transaction(async (tx) => {
|
||||||
const doc = await secretApprovalRequestDAL.create(
|
const doc = await secretApprovalRequestDAL.create(
|
||||||
{
|
{
|
||||||
folderId,
|
folderId,
|
||||||
@@ -1702,11 +1657,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return { ...doc, commits: approvalCommits };
|
return { ...doc, commits: approvalCommits };
|
||||||
};
|
});
|
||||||
|
|
||||||
const secretApprovalRequest = providedTx
|
|
||||||
? await executeApprovalRequestCreation(providedTx)
|
|
||||||
: await secretApprovalRequestDAL.transaction(executeApprovalRequestCreation);
|
|
||||||
|
|
||||||
const user = await userDAL.findById(actorId);
|
const user = await userDAL.findById(actorId);
|
||||||
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
||||||
|
@@ -21,8 +21,6 @@ const GRAPH_API_BASE = "https://graph.microsoft.com/v1.0";
|
|||||||
|
|
||||||
type AzureErrorResponse = { error: { message: string } };
|
type AzureErrorResponse = { error: { message: string } };
|
||||||
|
|
||||||
const EXPIRY_PADDING_IN_DAYS = 3;
|
|
||||||
|
|
||||||
const sleep = async () =>
|
const sleep = async () =>
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
setTimeout(resolve, 1000);
|
setTimeout(resolve, 1000);
|
||||||
@@ -35,8 +33,7 @@ export const azureClientSecretRotationFactory: TRotationFactory<
|
|||||||
const {
|
const {
|
||||||
connection,
|
connection,
|
||||||
parameters: { objectId, clientId: clientIdParam },
|
parameters: { objectId, clientId: clientIdParam },
|
||||||
secretsMapping,
|
secretsMapping
|
||||||
rotationInterval
|
|
||||||
} = secretRotation;
|
} = secretRotation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,7 +50,7 @@ export const azureClientSecretRotationFactory: TRotationFactory<
|
|||||||
)}-${now.getFullYear()}`;
|
)}-${now.getFullYear()}`;
|
||||||
|
|
||||||
const endDateTime = new Date();
|
const endDateTime = new Date();
|
||||||
endDateTime.setDate(now.getDate() + rotationInterval * 2 + EXPIRY_PADDING_IN_DAYS); // give 72 hour buffer
|
endDateTime.setFullYear(now.getFullYear() + 5);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await request.post<AzureAddPasswordResponse>(
|
const { data } = await request.post<AzureAddPasswordResponse>(
|
||||||
@@ -198,12 +195,6 @@ export const azureClientSecretRotationFactory: TRotationFactory<
|
|||||||
callback
|
callback
|
||||||
) => {
|
) => {
|
||||||
const credentials = await $rotateClientSecret();
|
const credentials = await $rotateClientSecret();
|
||||||
|
|
||||||
// 2.5 years as expiry is set to x2 interval for the inactive period of credential
|
|
||||||
if (rotationInterval > Math.floor(365 * 2.5) - EXPIRY_PADDING_IN_DAYS) {
|
|
||||||
throw new BadRequestError({ message: "Azure does not support token duration over 5 years" });
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(credentials);
|
return callback(credentials);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
export * from "./okta-client-secret-rotation-constants";
|
|
||||||
export * from "./okta-client-secret-rotation-schemas";
|
|
||||||
export * from "./okta-client-secret-rotation-types";
|
|
@@ -1,15 +0,0 @@
|
|||||||
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
|
||||||
import { TSecretRotationV2ListItem } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
|
||||||
|
|
||||||
export const OKTA_CLIENT_SECRET_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = {
|
|
||||||
name: "Okta Client Secret",
|
|
||||||
type: SecretRotation.OktaClientSecret,
|
|
||||||
connection: AppConnection.Okta,
|
|
||||||
template: {
|
|
||||||
secretsMapping: {
|
|
||||||
clientId: "OKTA_CLIENT_ID",
|
|
||||||
clientSecret: "OKTA_CLIENT_SECRET"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@@ -1,273 +0,0 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
|
||||||
import { AxiosError } from "axios";
|
|
||||||
|
|
||||||
import {
|
|
||||||
TRotationFactory,
|
|
||||||
TRotationFactoryGetSecretsPayload,
|
|
||||||
TRotationFactoryIssueCredentials,
|
|
||||||
TRotationFactoryRevokeCredentials,
|
|
||||||
TRotationFactoryRotateCredentials
|
|
||||||
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
|
||||||
import { request } from "@app/lib/config/request";
|
|
||||||
import { delay as delayMs } from "@app/lib/delay";
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
|
||||||
import { getOktaInstanceUrl } from "@app/services/app-connection/okta";
|
|
||||||
|
|
||||||
import {
|
|
||||||
TOktaClientSecret,
|
|
||||||
TOktaClientSecretRotationGeneratedCredentials,
|
|
||||||
TOktaClientSecretRotationWithConnection
|
|
||||||
} from "./okta-client-secret-rotation-types";
|
|
||||||
|
|
||||||
type OktaErrorResponse = { errorCode: string; errorSummary: string; errorCauses?: { errorSummary: string }[] };
|
|
||||||
|
|
||||||
const isOktaErrorResponse = (data: unknown): data is OktaErrorResponse => {
|
|
||||||
return (
|
|
||||||
typeof data === "object" &&
|
|
||||||
data !== null &&
|
|
||||||
"errorSummary" in data &&
|
|
||||||
typeof (data as OktaErrorResponse).errorSummary === "string"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const createErrorMessage = (error: unknown) => {
|
|
||||||
if (error instanceof AxiosError) {
|
|
||||||
if (error.response?.data && isOktaErrorResponse(error.response.data)) {
|
|
||||||
const oktaError = error.response.data;
|
|
||||||
if (oktaError.errorCauses && oktaError.errorCauses.length > 0) {
|
|
||||||
return oktaError.errorCauses[0].errorSummary;
|
|
||||||
}
|
|
||||||
return oktaError.errorSummary;
|
|
||||||
}
|
|
||||||
if (error.message) {
|
|
||||||
return error.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "Unknown error";
|
|
||||||
};
|
|
||||||
|
|
||||||
// Delay between each revocation call in revokeCredentials
|
|
||||||
const DELAY_MS = 1000;
|
|
||||||
|
|
||||||
export const oktaClientSecretRotationFactory: TRotationFactory<
|
|
||||||
TOktaClientSecretRotationWithConnection,
|
|
||||||
TOktaClientSecretRotationGeneratedCredentials
|
|
||||||
> = (secretRotation) => {
|
|
||||||
const {
|
|
||||||
connection,
|
|
||||||
parameters: { clientId },
|
|
||||||
secretsMapping
|
|
||||||
} = secretRotation;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new client secret for the Okta app.
|
|
||||||
*/
|
|
||||||
const $rotateClientSecret = async () => {
|
|
||||||
const instanceUrl = await getOktaInstanceUrl(connection);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { data } = await request.post<TOktaClientSecret>(
|
|
||||||
`${instanceUrl}/api/v1/apps/${clientId}/credentials/secrets`,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
Authorization: `SSWS ${connection.credentials.apiToken}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!data.client_secret || !data.id) {
|
|
||||||
throw new Error("Invalid response from Okta: missing 'client_secret' or secret 'id'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
clientSecret: data.client_secret,
|
|
||||||
secretId: data.id,
|
|
||||||
clientId
|
|
||||||
};
|
|
||||||
} catch (error: unknown) {
|
|
||||||
if (
|
|
||||||
error instanceof AxiosError &&
|
|
||||||
error.response?.data &&
|
|
||||||
isOktaErrorResponse(error.response.data) &&
|
|
||||||
error.response.data.errorCode === "E0000001"
|
|
||||||
) {
|
|
||||||
// Okta has a maximum of 2 secrets per app, thus we must warn the users in case they already have 2
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Failed to add client secret to Okta app ${clientId}: You must have only a single secret for the Okta app prior to creating this secret rotation.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Failed to add client secret to Okta app ${clientId}: ${createErrorMessage(error)}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List client secrets.
|
|
||||||
*/
|
|
||||||
const $listClientSecrets = async () => {
|
|
||||||
const instanceUrl = await getOktaInstanceUrl(connection);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { data } = await request.get<TOktaClientSecret[]>(
|
|
||||||
`${instanceUrl}/api/v1/apps/${clientId}/credentials/secrets`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
Authorization: `SSWS ${connection.credentials.apiToken}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
} catch (error: unknown) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Failed to list client secrets for Okta app ${clientId}: ${createErrorMessage(error)}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a credential with the given secretId exists.
|
|
||||||
*/
|
|
||||||
const credentialExists = async (secretId: string): Promise<boolean> => {
|
|
||||||
const instanceUrl = await getOktaInstanceUrl(connection);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { data } = await request.get<TOktaClientSecret>(
|
|
||||||
`${instanceUrl}/api/v1/apps/${clientId}/credentials/secrets/${secretId}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
Authorization: `SSWS ${connection.credentials.apiToken}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return data.id === secretId;
|
|
||||||
} catch (_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Revokes a client secret from the Okta app using its secretId.
|
|
||||||
* First checks if the credential exists before attempting revocation.
|
|
||||||
*/
|
|
||||||
const revokeCredential = async (secretId: string) => {
|
|
||||||
// Check if credential exists before attempting revocation
|
|
||||||
const exists = await credentialExists(secretId);
|
|
||||||
if (!exists) {
|
|
||||||
return; // Credential doesn't exist, nothing to revoke
|
|
||||||
}
|
|
||||||
|
|
||||||
const instanceUrl = await getOktaInstanceUrl(connection);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// First deactivate the secret
|
|
||||||
await request.post(
|
|
||||||
`${instanceUrl}/api/v1/apps/${clientId}/credentials/secrets/${secretId}/lifecycle/deactivate`,
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `SSWS ${connection.credentials.apiToken}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Then delete it
|
|
||||||
await request.delete(`${instanceUrl}/api/v1/apps/${clientId}/credentials/secrets/${secretId}`, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `SSWS ${connection.credentials.apiToken}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error: unknown) {
|
|
||||||
if (
|
|
||||||
error instanceof AxiosError &&
|
|
||||||
error.response?.data &&
|
|
||||||
isOktaErrorResponse(error.response.data) &&
|
|
||||||
error.response.data.errorCode === "E0000001"
|
|
||||||
) {
|
|
||||||
// If this is the last secret, we cannot revoke it
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Failed to remove client secret with secretId ${secretId} from app ${clientId}: ${createErrorMessage(error)}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Issues a new set of credentials.
|
|
||||||
*/
|
|
||||||
const issueCredentials: TRotationFactoryIssueCredentials<TOktaClientSecretRotationGeneratedCredentials> = async (
|
|
||||||
callback
|
|
||||||
) => {
|
|
||||||
const credentials = await $rotateClientSecret();
|
|
||||||
return callback(credentials);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Revokes a list of credentials.
|
|
||||||
*/
|
|
||||||
const revokeCredentials: TRotationFactoryRevokeCredentials<TOktaClientSecretRotationGeneratedCredentials> = async (
|
|
||||||
credentials,
|
|
||||||
callback
|
|
||||||
) => {
|
|
||||||
if (!credentials?.length) return callback();
|
|
||||||
|
|
||||||
for (const { secretId } of credentials) {
|
|
||||||
await revokeCredential(secretId);
|
|
||||||
await delayMs(DELAY_MS);
|
|
||||||
}
|
|
||||||
return callback();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rotates credentials by issuing new ones and revoking the old.
|
|
||||||
*/
|
|
||||||
const rotateCredentials: TRotationFactoryRotateCredentials<TOktaClientSecretRotationGeneratedCredentials> = async (
|
|
||||||
oldCredentials,
|
|
||||||
callback,
|
|
||||||
activeCredentials
|
|
||||||
) => {
|
|
||||||
// Since in Okta you can only have a maximum of 2 secrets at a time, we must delete any other secret besides the current one PRIOR to generating the second secret
|
|
||||||
if (oldCredentials?.secretId) {
|
|
||||||
await revokeCredential(oldCredentials.secretId);
|
|
||||||
} else if (activeCredentials) {
|
|
||||||
// On the first rotation oldCredentials won't be set so we must find the second secret manually
|
|
||||||
const secrets = await $listClientSecrets();
|
|
||||||
|
|
||||||
if (secrets.length > 1) {
|
|
||||||
const nonActiveSecret = secrets.find((secret) => secret.id !== activeCredentials.secretId);
|
|
||||||
if (nonActiveSecret) {
|
|
||||||
await revokeCredential(nonActiveSecret.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const newCredentials = await $rotateClientSecret();
|
|
||||||
return callback(newCredentials);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps the generated credentials into the secret payload format.
|
|
||||||
*/
|
|
||||||
const getSecretsPayload: TRotationFactoryGetSecretsPayload<TOktaClientSecretRotationGeneratedCredentials> = ({
|
|
||||||
clientSecret
|
|
||||||
}) => [
|
|
||||||
{ key: secretsMapping.clientId, value: clientId },
|
|
||||||
{ key: secretsMapping.clientSecret, value: clientSecret }
|
|
||||||
];
|
|
||||||
|
|
||||||
return {
|
|
||||||
issueCredentials,
|
|
||||||
revokeCredentials,
|
|
||||||
rotateCredentials,
|
|
||||||
getSecretsPayload
|
|
||||||
};
|
|
||||||
};
|
|
@@ -1,68 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
|
||||||
import {
|
|
||||||
BaseCreateSecretRotationSchema,
|
|
||||||
BaseSecretRotationSchema,
|
|
||||||
BaseUpdateSecretRotationSchema
|
|
||||||
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-schemas";
|
|
||||||
import { SecretRotations } from "@app/lib/api-docs";
|
|
||||||
import { SecretNameSchema } from "@app/server/lib/schemas";
|
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
|
||||||
|
|
||||||
export const OktaClientSecretRotationGeneratedCredentialsSchema = z
|
|
||||||
.object({
|
|
||||||
clientId: z.string(),
|
|
||||||
clientSecret: z.string(),
|
|
||||||
secretId: z.string()
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
.min(1)
|
|
||||||
.max(2);
|
|
||||||
|
|
||||||
const OktaClientSecretRotationParametersSchema = z.object({
|
|
||||||
clientId: z
|
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.min(1, "Client ID Required")
|
|
||||||
.describe(SecretRotations.PARAMETERS.OKTA_CLIENT_SECRET.clientId)
|
|
||||||
});
|
|
||||||
|
|
||||||
const OktaClientSecretRotationSecretsMappingSchema = z.object({
|
|
||||||
clientId: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.OKTA_CLIENT_SECRET.clientId),
|
|
||||||
clientSecret: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.OKTA_CLIENT_SECRET.clientSecret)
|
|
||||||
});
|
|
||||||
|
|
||||||
export const OktaClientSecretRotationTemplateSchema = z.object({
|
|
||||||
secretsMapping: z.object({
|
|
||||||
clientId: z.string(),
|
|
||||||
clientSecret: z.string()
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
export const OktaClientSecretRotationSchema = BaseSecretRotationSchema(SecretRotation.OktaClientSecret).extend({
|
|
||||||
type: z.literal(SecretRotation.OktaClientSecret),
|
|
||||||
parameters: OktaClientSecretRotationParametersSchema,
|
|
||||||
secretsMapping: OktaClientSecretRotationSecretsMappingSchema
|
|
||||||
});
|
|
||||||
|
|
||||||
export const CreateOktaClientSecretRotationSchema = BaseCreateSecretRotationSchema(
|
|
||||||
SecretRotation.OktaClientSecret
|
|
||||||
).extend({
|
|
||||||
parameters: OktaClientSecretRotationParametersSchema,
|
|
||||||
secretsMapping: OktaClientSecretRotationSecretsMappingSchema
|
|
||||||
});
|
|
||||||
|
|
||||||
export const UpdateOktaClientSecretRotationSchema = BaseUpdateSecretRotationSchema(
|
|
||||||
SecretRotation.OktaClientSecret
|
|
||||||
).extend({
|
|
||||||
parameters: OktaClientSecretRotationParametersSchema.optional(),
|
|
||||||
secretsMapping: OktaClientSecretRotationSecretsMappingSchema.optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
export const OktaClientSecretRotationListItemSchema = z.object({
|
|
||||||
name: z.literal("Okta Client Secret"),
|
|
||||||
connection: z.literal(AppConnection.Okta),
|
|
||||||
type: z.literal(SecretRotation.OktaClientSecret),
|
|
||||||
template: OktaClientSecretRotationTemplateSchema
|
|
||||||
});
|
|
@@ -1,40 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { TOktaConnection } from "@app/services/app-connection/okta";
|
|
||||||
|
|
||||||
import {
|
|
||||||
CreateOktaClientSecretRotationSchema,
|
|
||||||
OktaClientSecretRotationGeneratedCredentialsSchema,
|
|
||||||
OktaClientSecretRotationListItemSchema,
|
|
||||||
OktaClientSecretRotationSchema
|
|
||||||
} from "./okta-client-secret-rotation-schemas";
|
|
||||||
|
|
||||||
export type TOktaClientSecretRotation = z.infer<typeof OktaClientSecretRotationSchema>;
|
|
||||||
|
|
||||||
export type TOktaClientSecretRotationInput = z.infer<typeof CreateOktaClientSecretRotationSchema>;
|
|
||||||
|
|
||||||
export type TOktaClientSecretRotationListItem = z.infer<typeof OktaClientSecretRotationListItemSchema>;
|
|
||||||
|
|
||||||
export type TOktaClientSecretRotationWithConnection = TOktaClientSecretRotation & {
|
|
||||||
connection: TOktaConnection;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TOktaClientSecretRotationGeneratedCredentials = z.infer<
|
|
||||||
typeof OktaClientSecretRotationGeneratedCredentialsSchema
|
|
||||||
>;
|
|
||||||
|
|
||||||
export interface TOktaClientSecretRotationParameters {
|
|
||||||
clientId: string;
|
|
||||||
secretId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TOktaClientSecretRotationSecretsMapping {
|
|
||||||
clientId: string;
|
|
||||||
clientSecret: string;
|
|
||||||
secretId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TOktaClientSecret {
|
|
||||||
id: string;
|
|
||||||
client_secret: string;
|
|
||||||
}
|
|
@@ -51,7 +51,6 @@ const baseSecretRotationV2Query = ({
|
|||||||
db.ref("encryptedCredentials").withSchema(TableName.AppConnection).as("connectionEncryptedCredentials"),
|
db.ref("encryptedCredentials").withSchema(TableName.AppConnection).as("connectionEncryptedCredentials"),
|
||||||
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
|
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
|
||||||
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
|
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
|
||||||
db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"),
|
|
||||||
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
|
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
|
||||||
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
|
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
|
||||||
db
|
db
|
||||||
@@ -105,7 +104,6 @@ const expandSecretRotation = <T extends Awaited<ReturnType<typeof baseSecretRota
|
|||||||
connectionCreatedAt,
|
connectionCreatedAt,
|
||||||
connectionUpdatedAt,
|
connectionUpdatedAt,
|
||||||
connectionVersion,
|
connectionVersion,
|
||||||
connectionGatewayId,
|
|
||||||
connectionIsPlatformManagedCredentials,
|
connectionIsPlatformManagedCredentials,
|
||||||
...el
|
...el
|
||||||
} = secretRotation;
|
} = secretRotation;
|
||||||
@@ -125,7 +123,6 @@ const expandSecretRotation = <T extends Awaited<ReturnType<typeof baseSecretRota
|
|||||||
createdAt: connectionCreatedAt,
|
createdAt: connectionCreatedAt,
|
||||||
updatedAt: connectionUpdatedAt,
|
updatedAt: connectionUpdatedAt,
|
||||||
version: connectionVersion,
|
version: connectionVersion,
|
||||||
gatewayId: connectionGatewayId,
|
|
||||||
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials
|
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials
|
||||||
},
|
},
|
||||||
folder: {
|
folder: {
|
||||||
|
@@ -6,8 +6,7 @@ export enum SecretRotation {
|
|||||||
Auth0ClientSecret = "auth0-client-secret",
|
Auth0ClientSecret = "auth0-client-secret",
|
||||||
AzureClientSecret = "azure-client-secret",
|
AzureClientSecret = "azure-client-secret",
|
||||||
AwsIamUserSecret = "aws-iam-user-secret",
|
AwsIamUserSecret = "aws-iam-user-secret",
|
||||||
LdapPassword = "ldap-password",
|
LdapPassword = "ldap-password"
|
||||||
OktaClientSecret = "okta-client-secret"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretRotationStatus {
|
export enum SecretRotationStatus {
|
||||||
|
@@ -10,7 +10,6 @@ import { AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./azure-client-secret"
|
|||||||
import { LDAP_PASSWORD_ROTATION_LIST_OPTION, TLdapPasswordRotation } from "./ldap-password";
|
import { LDAP_PASSWORD_ROTATION_LIST_OPTION, TLdapPasswordRotation } from "./ldap-password";
|
||||||
import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials";
|
import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials";
|
||||||
import { MYSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mysql-credentials";
|
import { MYSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mysql-credentials";
|
||||||
import { OKTA_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./okta-client-secret";
|
|
||||||
import { ORACLEDB_CREDENTIALS_ROTATION_LIST_OPTION } from "./oracledb-credentials";
|
import { ORACLEDB_CREDENTIALS_ROTATION_LIST_OPTION } from "./oracledb-credentials";
|
||||||
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
|
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
|
||||||
import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums";
|
import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums";
|
||||||
@@ -31,8 +30,7 @@ const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2List
|
|||||||
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.AzureClientSecret]: AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
[SecretRotation.AzureClientSecret]: AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION,
|
[SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.LdapPassword]: LDAP_PASSWORD_ROTATION_LIST_OPTION,
|
[SecretRotation.LdapPassword]: LDAP_PASSWORD_ROTATION_LIST_OPTION
|
||||||
[SecretRotation.OktaClientSecret]: OKTA_CLIENT_SECRET_ROTATION_LIST_OPTION
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listSecretRotationOptions = () => {
|
export const listSecretRotationOptions = () => {
|
||||||
|
@@ -9,8 +9,7 @@ export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
|
|||||||
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
|
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
|
||||||
[SecretRotation.AzureClientSecret]: "Azure Client Secret",
|
[SecretRotation.AzureClientSecret]: "Azure Client Secret",
|
||||||
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret",
|
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret",
|
||||||
[SecretRotation.LdapPassword]: "LDAP Password",
|
[SecretRotation.LdapPassword]: "LDAP Password"
|
||||||
[SecretRotation.OktaClientSecret]: "Okta Client Secret"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
|
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
|
||||||
@@ -21,6 +20,5 @@ export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnectio
|
|||||||
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
|
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
|
||||||
[SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets,
|
[SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets,
|
||||||
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS,
|
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS,
|
||||||
[SecretRotation.LdapPassword]: AppConnection.LDAP,
|
[SecretRotation.LdapPassword]: AppConnection.LDAP
|
||||||
[SecretRotation.OktaClientSecret]: AppConnection.Okta
|
|
||||||
};
|
};
|
||||||
|
@@ -167,7 +167,7 @@ export const secretRotationV2QueueServiceFactory = async ({
|
|||||||
environment: environment.name,
|
environment: environment.name,
|
||||||
projectName: project.name,
|
projectName: project.name,
|
||||||
rotationUrl: encodeURI(
|
rotationUrl: encodeURI(
|
||||||
`${appCfg.SITE_URL}/projects/secret-management/${projectId}/secrets/${environment.slug}`
|
`${appCfg.SITE_URL}/projects/${projectId}/secret-manager/secrets/${environment.slug}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -2,9 +2,8 @@ import { ForbiddenError, subject } from "@casl/ability";
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
import isEqual from "lodash.isequal";
|
import isEqual from "lodash.isequal";
|
||||||
|
|
||||||
import { ActionProjectType, SecretType, TableName } from "@app/db/schemas";
|
import { SecretType, TableName } from "@app/db/schemas";
|
||||||
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { hasSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns";
|
import { hasSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
@@ -83,7 +82,6 @@ import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secre
|
|||||||
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
||||||
|
|
||||||
import { awsIamUserSecretRotationFactory } from "./aws-iam-user-secret/aws-iam-user-secret-rotation-fns";
|
import { awsIamUserSecretRotationFactory } from "./aws-iam-user-secret/aws-iam-user-secret-rotation-fns";
|
||||||
import { oktaClientSecretRotationFactory } from "./okta-client-secret/okta-client-secret-rotation-fns";
|
|
||||||
import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
|
import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
|
||||||
|
|
||||||
export type TSecretRotationV2ServiceFactoryDep = {
|
export type TSecretRotationV2ServiceFactoryDep = {
|
||||||
@@ -109,7 +107,6 @@ export type TSecretRotationV2ServiceFactoryDep = {
|
|||||||
queueService: Pick<TQueueServiceFactory, "queuePg">;
|
queueService: Pick<TQueueServiceFactory, "queuePg">;
|
||||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">;
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">;
|
||||||
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
||||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretRotationV2ServiceFactory = ReturnType<typeof secretRotationV2ServiceFactory>;
|
export type TSecretRotationV2ServiceFactory = ReturnType<typeof secretRotationV2ServiceFactory>;
|
||||||
@@ -129,8 +126,7 @@ const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplem
|
|||||||
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.AzureClientSecret]: azureClientSecretRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.AzureClientSecret]: azureClientSecretRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.LdapPassword]: ldapPasswordRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.LdapPassword]: ldapPasswordRotationFactory as TRotationFactoryImplementation
|
||||||
[SecretRotation.OktaClientSecret]: oktaClientSecretRotationFactory as TRotationFactoryImplementation
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const secretRotationV2ServiceFactory = ({
|
export const secretRotationV2ServiceFactory = ({
|
||||||
@@ -152,8 +148,7 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
keyStore,
|
keyStore,
|
||||||
queueService,
|
queueService,
|
||||||
folderCommitService,
|
folderCommitService,
|
||||||
appConnectionDAL,
|
appConnectionDAL
|
||||||
gatewayService
|
|
||||||
}: TSecretRotationV2ServiceFactoryDep) => {
|
}: TSecretRotationV2ServiceFactoryDep) => {
|
||||||
const $queueSendSecretRotationStatusNotification = async (secretRotation: TSecretRotationV2Raw) => {
|
const $queueSendSecretRotationStatusNotification = async (secretRotation: TSecretRotationV2Raw) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
@@ -223,7 +218,7 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
actorAuthMethod: actor.authMethod,
|
actorAuthMethod: actor.authMethod,
|
||||||
actorOrgId: actor.orgId,
|
actorOrgId: actor.orgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager,
|
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -274,7 +269,7 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
actorAuthMethod: actor.authMethod,
|
actorAuthMethod: actor.authMethod,
|
||||||
actorOrgId: actor.orgId,
|
actorOrgId: actor.orgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager,
|
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -320,7 +315,7 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
actorAuthMethod: actor.authMethod,
|
actorAuthMethod: actor.authMethod,
|
||||||
actorOrgId: actor.orgId,
|
actorOrgId: actor.orgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager,
|
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -385,7 +380,7 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
actorAuthMethod: actor.authMethod,
|
actorAuthMethod: actor.authMethod,
|
||||||
actorOrgId: actor.orgId,
|
actorOrgId: actor.orgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager,
|
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -429,7 +424,7 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
actorAuthMethod: actor.authMethod,
|
actorAuthMethod: actor.authMethod,
|
||||||
actorOrgId: actor.orgId,
|
actorOrgId: actor.orgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager,
|
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -466,8 +461,7 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
rotationInterval: payload.rotationInterval
|
rotationInterval: payload.rotationInterval
|
||||||
} as TSecretRotationV2WithConnection,
|
} as TSecretRotationV2WithConnection,
|
||||||
appConnectionDAL,
|
appConnectionDAL,
|
||||||
kmsService,
|
kmsService
|
||||||
gatewayService
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// even though we have a db constraint we want to check before any rotation of credentials is attempted
|
// even though we have a db constraint we want to check before any rotation of credentials is attempted
|
||||||
@@ -631,7 +625,7 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
actorAuthMethod: actor.authMethod,
|
actorAuthMethod: actor.authMethod,
|
||||||
actorOrgId: actor.orgId,
|
actorOrgId: actor.orgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager,
|
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -781,7 +775,7 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
actorAuthMethod: actor.authMethod,
|
actorAuthMethod: actor.authMethod,
|
||||||
actorOrgId: actor.orgId,
|
actorOrgId: actor.orgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager,
|
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -830,8 +824,7 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
connection: appConnection
|
connection: appConnection
|
||||||
} as TSecretRotationV2WithConnection,
|
} as TSecretRotationV2WithConnection,
|
||||||
appConnectionDAL,
|
appConnectionDAL,
|
||||||
kmsService,
|
kmsService
|
||||||
gatewayService
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const generatedCredentials = await decryptSecretRotationCredentials({
|
const generatedCredentials = await decryptSecretRotationCredentials({
|
||||||
@@ -914,8 +907,7 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
connection: appConnection
|
connection: appConnection
|
||||||
} as TSecretRotationV2WithConnection,
|
} as TSecretRotationV2WithConnection,
|
||||||
appConnectionDAL,
|
appConnectionDAL,
|
||||||
kmsService,
|
kmsService
|
||||||
gatewayService
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const updatedRotation = await rotationFactory.rotateCredentials(
|
const updatedRotation = await rotationFactory.rotateCredentials(
|
||||||
@@ -1113,7 +1105,7 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
actorAuthMethod: actor.authMethod,
|
actorAuthMethod: actor.authMethod,
|
||||||
actorOrgId: actor.orgId,
|
actorOrgId: actor.orgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager,
|
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1160,7 +1152,7 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
actorAuthMethod: actor.authMethod,
|
actorAuthMethod: actor.authMethod,
|
||||||
actorOrgId: actor.orgId,
|
actorOrgId: actor.orgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager,
|
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1212,7 +1204,7 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
actorAuthMethod: actor.authMethod,
|
actorAuthMethod: actor.authMethod,
|
||||||
actorOrgId: actor.orgId,
|
actorOrgId: actor.orgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager,
|
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1328,8 +1320,7 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod: actor.authMethod,
|
actorAuthMethod: actor.authMethod,
|
||||||
actorOrgId: actor.orgId,
|
actorOrgId: actor.orgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const permissiveFolderMappings = folderMappings.filter(({ path, environment }) =>
|
const permissiveFolderMappings = folderMappings.filter(({ path, environment }) =>
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import { AuditLogInfo } from "@app/ee/services/audit-log/audit-log-types";
|
import { AuditLogInfo } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
|
||||||
import { TSqlCredentialsRotationGeneratedCredentials } from "@app/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-types";
|
import { TSqlCredentialsRotationGeneratedCredentials } from "@app/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-types";
|
||||||
import { OrderByDirection } from "@app/lib/types";
|
import { OrderByDirection } from "@app/lib/types";
|
||||||
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||||
@@ -46,13 +45,6 @@ import {
|
|||||||
TMySqlCredentialsRotationListItem,
|
TMySqlCredentialsRotationListItem,
|
||||||
TMySqlCredentialsRotationWithConnection
|
TMySqlCredentialsRotationWithConnection
|
||||||
} from "./mysql-credentials";
|
} from "./mysql-credentials";
|
||||||
import {
|
|
||||||
TOktaClientSecretRotation,
|
|
||||||
TOktaClientSecretRotationGeneratedCredentials,
|
|
||||||
TOktaClientSecretRotationInput,
|
|
||||||
TOktaClientSecretRotationListItem,
|
|
||||||
TOktaClientSecretRotationWithConnection
|
|
||||||
} from "./okta-client-secret";
|
|
||||||
import {
|
import {
|
||||||
TOracleDBCredentialsRotation,
|
TOracleDBCredentialsRotation,
|
||||||
TOracleDBCredentialsRotationInput,
|
TOracleDBCredentialsRotationInput,
|
||||||
@@ -76,8 +68,7 @@ export type TSecretRotationV2 =
|
|||||||
| TAuth0ClientSecretRotation
|
| TAuth0ClientSecretRotation
|
||||||
| TAzureClientSecretRotation
|
| TAzureClientSecretRotation
|
||||||
| TLdapPasswordRotation
|
| TLdapPasswordRotation
|
||||||
| TAwsIamUserSecretRotation
|
| TAwsIamUserSecretRotation;
|
||||||
| TOktaClientSecretRotation;
|
|
||||||
|
|
||||||
export type TSecretRotationV2WithConnection =
|
export type TSecretRotationV2WithConnection =
|
||||||
| TPostgresCredentialsRotationWithConnection
|
| TPostgresCredentialsRotationWithConnection
|
||||||
@@ -87,16 +78,14 @@ export type TSecretRotationV2WithConnection =
|
|||||||
| TAuth0ClientSecretRotationWithConnection
|
| TAuth0ClientSecretRotationWithConnection
|
||||||
| TAzureClientSecretRotationWithConnection
|
| TAzureClientSecretRotationWithConnection
|
||||||
| TLdapPasswordRotationWithConnection
|
| TLdapPasswordRotationWithConnection
|
||||||
| TAwsIamUserSecretRotationWithConnection
|
| TAwsIamUserSecretRotationWithConnection;
|
||||||
| TOktaClientSecretRotationWithConnection;
|
|
||||||
|
|
||||||
export type TSecretRotationV2GeneratedCredentials =
|
export type TSecretRotationV2GeneratedCredentials =
|
||||||
| TSqlCredentialsRotationGeneratedCredentials
|
| TSqlCredentialsRotationGeneratedCredentials
|
||||||
| TAuth0ClientSecretRotationGeneratedCredentials
|
| TAuth0ClientSecretRotationGeneratedCredentials
|
||||||
| TAzureClientSecretRotationGeneratedCredentials
|
| TAzureClientSecretRotationGeneratedCredentials
|
||||||
| TLdapPasswordRotationGeneratedCredentials
|
| TLdapPasswordRotationGeneratedCredentials
|
||||||
| TAwsIamUserSecretRotationGeneratedCredentials
|
| TAwsIamUserSecretRotationGeneratedCredentials;
|
||||||
| TOktaClientSecretRotationGeneratedCredentials;
|
|
||||||
|
|
||||||
export type TSecretRotationV2Input =
|
export type TSecretRotationV2Input =
|
||||||
| TPostgresCredentialsRotationInput
|
| TPostgresCredentialsRotationInput
|
||||||
@@ -106,8 +95,7 @@ export type TSecretRotationV2Input =
|
|||||||
| TAuth0ClientSecretRotationInput
|
| TAuth0ClientSecretRotationInput
|
||||||
| TAzureClientSecretRotationInput
|
| TAzureClientSecretRotationInput
|
||||||
| TLdapPasswordRotationInput
|
| TLdapPasswordRotationInput
|
||||||
| TAwsIamUserSecretRotationInput
|
| TAwsIamUserSecretRotationInput;
|
||||||
| TOktaClientSecretRotationInput;
|
|
||||||
|
|
||||||
export type TSecretRotationV2ListItem =
|
export type TSecretRotationV2ListItem =
|
||||||
| TPostgresCredentialsRotationListItem
|
| TPostgresCredentialsRotationListItem
|
||||||
@@ -117,8 +105,7 @@ export type TSecretRotationV2ListItem =
|
|||||||
| TAuth0ClientSecretRotationListItem
|
| TAuth0ClientSecretRotationListItem
|
||||||
| TAzureClientSecretRotationListItem
|
| TAzureClientSecretRotationListItem
|
||||||
| TLdapPasswordRotationListItem
|
| TLdapPasswordRotationListItem
|
||||||
| TAwsIamUserSecretRotationListItem
|
| TAwsIamUserSecretRotationListItem;
|
||||||
| TOktaClientSecretRotationListItem;
|
|
||||||
|
|
||||||
export type TSecretRotationV2TemporaryParameters = TLdapPasswordRotationInput["temporaryParameters"] | undefined;
|
export type TSecretRotationV2TemporaryParameters = TLdapPasswordRotationInput["temporaryParameters"] | undefined;
|
||||||
|
|
||||||
@@ -252,8 +239,7 @@ export type TRotationFactory<
|
|||||||
> = (
|
> = (
|
||||||
secretRotation: T,
|
secretRotation: T,
|
||||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">,
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">,
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">,
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
|
||||||
) => {
|
) => {
|
||||||
issueCredentials: TRotationFactoryIssueCredentials<C, P>;
|
issueCredentials: TRotationFactoryIssueCredentials<C, P>;
|
||||||
revokeCredentials: TRotationFactoryRevokeCredentials<C>;
|
revokeCredentials: TRotationFactoryRevokeCredentials<C>;
|
||||||
|
@@ -6,7 +6,6 @@ import { AzureClientSecretRotationSchema } from "@app/ee/services/secret-rotatio
|
|||||||
import { LdapPasswordRotationSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
import { LdapPasswordRotationSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||||
import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||||
import { MySqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
|
import { MySqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
|
||||||
import { OktaClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/okta-client-secret";
|
|
||||||
import { OracleDBCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/oracledb-credentials";
|
import { OracleDBCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/oracledb-credentials";
|
||||||
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||||
|
|
||||||
@@ -18,6 +17,5 @@ export const SecretRotationV2Schema = z.discriminatedUnion("type", [
|
|||||||
Auth0ClientSecretRotationSchema,
|
Auth0ClientSecretRotationSchema,
|
||||||
AzureClientSecretRotationSchema,
|
AzureClientSecretRotationSchema,
|
||||||
LdapPasswordRotationSchema,
|
LdapPasswordRotationSchema,
|
||||||
AwsIamUserSecretRotationSchema,
|
AwsIamUserSecretRotationSchema
|
||||||
OktaClientSecretRotationSchema
|
|
||||||
]);
|
]);
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TRotationFactory,
|
TRotationFactory,
|
||||||
TRotationFactoryGetSecretsPayload,
|
TRotationFactoryGetSecretsPayload,
|
||||||
@@ -7,13 +5,9 @@ import {
|
|||||||
TRotationFactoryRevokeCredentials,
|
TRotationFactoryRevokeCredentials,
|
||||||
TRotationFactoryRotateCredentials
|
TRotationFactoryRotateCredentials
|
||||||
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
import { getSqlConnectionClient, SQL_CONNECTION_ALTER_LOGIN_STATEMENT } from "@app/services/app-connection/shared/sql";
|
||||||
import {
|
|
||||||
executeWithPotentialGateway,
|
|
||||||
SQL_CONNECTION_ALTER_LOGIN_STATEMENT
|
|
||||||
} from "@app/services/app-connection/shared/sql";
|
|
||||||
|
|
||||||
import { DEFAULT_PASSWORD_REQUIREMENTS, generatePassword } from "../utils";
|
import { generatePassword } from "../utils";
|
||||||
import {
|
import {
|
||||||
TSqlCredentialsRotationGeneratedCredentials,
|
TSqlCredentialsRotationGeneratedCredentials,
|
||||||
TSqlCredentialsRotationWithConnection
|
TSqlCredentialsRotationWithConnection
|
||||||
@@ -33,15 +27,10 @@ const redactPasswords = (e: unknown, credentials: TSqlCredentialsRotationGenerat
|
|||||||
return redactedMessage;
|
return redactedMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ORACLE_PASSWORD_REQUIREMENTS = {
|
|
||||||
...DEFAULT_PASSWORD_REQUIREMENTS,
|
|
||||||
length: 30
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sqlCredentialsRotationFactory: TRotationFactory<
|
export const sqlCredentialsRotationFactory: TRotationFactory<
|
||||||
TSqlCredentialsRotationWithConnection,
|
TSqlCredentialsRotationWithConnection,
|
||||||
TSqlCredentialsRotationGeneratedCredentials
|
TSqlCredentialsRotationGeneratedCredentials
|
||||||
> = (secretRotation, _appConnectionDAL, _kmsService, gatewayService) => {
|
> = (secretRotation) => {
|
||||||
const {
|
const {
|
||||||
connection,
|
connection,
|
||||||
parameters: { username1, username2 },
|
parameters: { username1, username2 },
|
||||||
@@ -49,60 +38,46 @@ export const sqlCredentialsRotationFactory: TRotationFactory<
|
|||||||
secretsMapping
|
secretsMapping
|
||||||
} = secretRotation;
|
} = secretRotation;
|
||||||
|
|
||||||
const passwordRequirement =
|
|
||||||
connection.app === AppConnection.OracleDB ? ORACLE_PASSWORD_REQUIREMENTS : DEFAULT_PASSWORD_REQUIREMENTS;
|
|
||||||
|
|
||||||
const executeOperation = <T>(
|
|
||||||
operation: (client: Knex) => Promise<T>,
|
|
||||||
credentialsOverride?: TSqlCredentialsRotationGeneratedCredentials[number]
|
|
||||||
) => {
|
|
||||||
const finalCredentials = {
|
|
||||||
...connection.credentials,
|
|
||||||
...credentialsOverride
|
|
||||||
};
|
|
||||||
|
|
||||||
return executeWithPotentialGateway(
|
|
||||||
{
|
|
||||||
...connection,
|
|
||||||
credentials: finalCredentials
|
|
||||||
},
|
|
||||||
gatewayService,
|
|
||||||
(client) => operation(client)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const $validateCredentials = async (credentials: TSqlCredentialsRotationGeneratedCredentials[number]) => {
|
const $validateCredentials = async (credentials: TSqlCredentialsRotationGeneratedCredentials[number]) => {
|
||||||
|
const client = await getSqlConnectionClient({
|
||||||
|
...connection,
|
||||||
|
credentials: {
|
||||||
|
...connection.credentials,
|
||||||
|
...credentials
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await executeOperation(async (client) => {
|
await client.raw("SELECT 1");
|
||||||
await client.raw(connection.app === AppConnection.OracleDB ? `SELECT 1 FROM DUAL` : `Select 1`);
|
|
||||||
}, credentials);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(redactPasswords(error, [credentials]));
|
throw new Error(redactPasswords(error, [credentials]));
|
||||||
|
} finally {
|
||||||
|
await client.destroy();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const issueCredentials: TRotationFactoryIssueCredentials<TSqlCredentialsRotationGeneratedCredentials> = async (
|
const issueCredentials: TRotationFactoryIssueCredentials<TSqlCredentialsRotationGeneratedCredentials> = async (
|
||||||
callback
|
callback
|
||||||
) => {
|
) => {
|
||||||
// For SQL, since we get existing users, we change both their passwords
|
const client = await getSqlConnectionClient(connection);
|
||||||
// on issue to invalidate their existing passwords
|
|
||||||
// For SQL, since we get existing users, we change both their passwords
|
// For SQL, since we get existing users, we change both their passwords
|
||||||
// on issue to invalidate their existing passwords
|
// on issue to invalidate their existing passwords
|
||||||
const credentialsSet = [
|
const credentialsSet = [
|
||||||
{ username: username1, password: generatePassword(passwordRequirement) },
|
{ username: username1, password: generatePassword() },
|
||||||
{ username: username2, password: generatePassword(passwordRequirement) }
|
{ username: username2, password: generatePassword() }
|
||||||
];
|
];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await executeOperation(async (client) => {
|
await client.transaction(async (tx) => {
|
||||||
await client.transaction(async (tx) => {
|
for await (const credentials of credentialsSet) {
|
||||||
for await (const credentials of credentialsSet) {
|
await tx.raw(...SQL_CONNECTION_ALTER_LOGIN_STATEMENT[connection.app](credentials));
|
||||||
await tx.raw(...SQL_CONNECTION_ALTER_LOGIN_STATEMENT[connection.app](credentials));
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(redactPasswords(error, credentialsSet));
|
throw new Error(redactPasswords(error, credentialsSet));
|
||||||
|
} finally {
|
||||||
|
await client.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const credentials of credentialsSet) {
|
for await (const credentials of credentialsSet) {
|
||||||
@@ -116,22 +91,21 @@ export const sqlCredentialsRotationFactory: TRotationFactory<
|
|||||||
credentialsToRevoke,
|
credentialsToRevoke,
|
||||||
callback
|
callback
|
||||||
) => {
|
) => {
|
||||||
const revokedCredentials = credentialsToRevoke.map(({ username }) => ({
|
const client = await getSqlConnectionClient(connection);
|
||||||
username,
|
|
||||||
password: generatePassword(passwordRequirement)
|
const revokedCredentials = credentialsToRevoke.map(({ username }) => ({ username, password: generatePassword() }));
|
||||||
}));
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await executeOperation(async (client) => {
|
await client.transaction(async (tx) => {
|
||||||
await client.transaction(async (tx) => {
|
for await (const credentials of revokedCredentials) {
|
||||||
for await (const credentials of revokedCredentials) {
|
// invalidate previous passwords
|
||||||
// invalidate previous passwords
|
await tx.raw(...SQL_CONNECTION_ALTER_LOGIN_STATEMENT[connection.app](credentials));
|
||||||
await tx.raw(...SQL_CONNECTION_ALTER_LOGIN_STATEMENT[connection.app](credentials));
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(redactPasswords(error, revokedCredentials));
|
throw new Error(redactPasswords(error, revokedCredentials));
|
||||||
|
} finally {
|
||||||
|
await client.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback();
|
return callback();
|
||||||
@@ -141,18 +115,17 @@ export const sqlCredentialsRotationFactory: TRotationFactory<
|
|||||||
_,
|
_,
|
||||||
callback
|
callback
|
||||||
) => {
|
) => {
|
||||||
|
const client = await getSqlConnectionClient(connection);
|
||||||
|
|
||||||
// generate new password for the next active user
|
// generate new password for the next active user
|
||||||
const credentials = {
|
const credentials = { username: activeIndex === 0 ? username2 : username1, password: generatePassword() };
|
||||||
username: activeIndex === 0 ? username2 : username1,
|
|
||||||
password: generatePassword(passwordRequirement)
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await executeOperation(async (client) => {
|
await client.raw(...SQL_CONNECTION_ALTER_LOGIN_STATEMENT[connection.app](credentials));
|
||||||
await client.raw(...SQL_CONNECTION_ALTER_LOGIN_STATEMENT[connection.app](credentials));
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(redactPasswords(error, [credentials]));
|
throw new Error(redactPasswords(error, [credentials]));
|
||||||
|
} finally {
|
||||||
|
await client.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
await $validateCredentials(credentials);
|
await $validateCredentials(credentials);
|
||||||
|
@@ -11,7 +11,7 @@ type TPasswordRequirements = {
|
|||||||
allowedSymbols?: string;
|
allowedSymbols?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_PASSWORD_REQUIREMENTS: TPasswordRequirements = {
|
const DEFAULT_PASSWORD_REQUIREMENTS: TPasswordRequirements = {
|
||||||
length: 48,
|
length: 48,
|
||||||
required: {
|
required: {
|
||||||
lowercase: 1,
|
lowercase: 1,
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import Ajv from "ajv";
|
import Ajv from "ajv";
|
||||||
|
|
||||||
import { ActionProjectType, ProjectVersion, TableName } from "@app/db/schemas";
|
import { ProjectVersion, TableName } from "@app/db/schemas";
|
||||||
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
|
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
@@ -66,8 +66,7 @@ export const secretRotationServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretRotationActions.Read,
|
ProjectPermissionSecretRotationActions.Read,
|
||||||
@@ -98,8 +97,7 @@ export const secretRotationServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretRotationActions.Read,
|
ProjectPermissionSecretRotationActions.Read,
|
||||||
@@ -215,8 +213,7 @@ export const secretRotationServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretRotationActions.Read,
|
ProjectPermissionSecretRotationActions.Read,
|
||||||
@@ -266,8 +263,7 @@ export const secretRotationServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretRotationActions.Edit,
|
ProjectPermissionSecretRotationActions.Edit,
|
||||||
@@ -287,8 +283,7 @@ export const secretRotationServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
projectId: doc.projectId,
|
projectId: doc.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretRotationActions.Delete,
|
ProjectPermissionSecretRotationActions.Delete,
|
||||||
|
@@ -18,8 +18,7 @@ import {
|
|||||||
TSecretScanningFactoryInitialize,
|
TSecretScanningFactoryInitialize,
|
||||||
TSecretScanningFactoryListRawResources,
|
TSecretScanningFactoryListRawResources,
|
||||||
TSecretScanningFactoryPostInitialization,
|
TSecretScanningFactoryPostInitialization,
|
||||||
TSecretScanningFactoryTeardown,
|
TSecretScanningFactoryTeardown
|
||||||
TSecretScanningFactoryValidateConfigUpdate
|
|
||||||
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-types";
|
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-types";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { request } from "@app/lib/config/request";
|
import { request } from "@app/lib/config/request";
|
||||||
@@ -303,13 +302,6 @@ export const BitbucketSecretScanningFactory = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateConfigUpdate: TSecretScanningFactoryValidateConfigUpdate<
|
|
||||||
TBitbucketDataSourceInput["config"],
|
|
||||||
TBitbucketDataSourceWithConnection
|
|
||||||
> = async () => {
|
|
||||||
// no validation required
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
initialize,
|
initialize,
|
||||||
postInitialization,
|
postInitialization,
|
||||||
@@ -317,7 +309,6 @@ export const BitbucketSecretScanningFactory = () => {
|
|||||||
getFullScanPath,
|
getFullScanPath,
|
||||||
getDiffScanResourcePayload,
|
getDiffScanResourcePayload,
|
||||||
getDiffScanFindingsPayload,
|
getDiffScanFindingsPayload,
|
||||||
teardown,
|
teardown
|
||||||
validateConfigUpdate
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user