mirror of
https://github.com/Infisical/infisical.git
synced 2025-08-09 06:02:53 +00:00
Compare commits
108 Commits
help-fix-f
...
misc/add-c
Author | SHA1 | Date | |
---|---|---|---|
|
06a7e804eb | ||
|
0f00474243 | ||
|
3df010f266 | ||
|
333ce9d164 | ||
|
9621df4f8b | ||
|
3f2de2c5ef | ||
|
b2b1c13393 | ||
|
1fb0c638d6 | ||
|
c1ad49a532 | ||
|
d1fcc739c9 | ||
|
c7458d94aa | ||
|
93570df318 | ||
|
e798b4a7ba | ||
|
36c93f47d9 | ||
|
dbbcb157ef | ||
|
bdc23d22e7 | ||
|
08c1740afc | ||
|
3cac4ef927 | ||
|
2667f8f0f2 | ||
|
b39537472b | ||
|
6b60b2562d | ||
|
c2a7827080 | ||
|
64e09b0dcd | ||
|
a7176d44dd | ||
|
09d4cdc634 | ||
|
8a93c0bd59 | ||
|
c0f8f50981 | ||
|
fec47ef81c | ||
|
348f4b9787 | ||
|
aa577b095c | ||
|
f515cc83d7 | ||
|
17bbdbe7bb | ||
|
427de068d5 | ||
|
dbf7ecc9b6 | ||
|
1ef9885062 | ||
|
de48c3e161 | ||
|
852664e2cb | ||
|
fbc8264732 | ||
|
4303547d8c | ||
|
f1c8a66d31 | ||
|
baa05714ab | ||
|
0c21c19c95 | ||
|
c487614c38 | ||
|
a55c8cacea | ||
|
62308fb0a3 | ||
|
55aa1e87c0 | ||
|
c5c7adbc42 | ||
|
f686882ce6 | ||
|
e35417e11b | ||
|
ff0f4cf46a | ||
|
53968e07d0 | ||
|
64093e9175 | ||
|
c315eed4d4 | ||
|
78fd852588 | ||
|
0c1f761a9a | ||
|
c363f485eb | ||
|
433d83641d | ||
|
35bb7f299c | ||
|
160e2b773b | ||
|
f0a70e23ac | ||
|
a6271a6187 | ||
|
b2fbec740f | ||
|
86e5f46d89 | ||
|
720789025c | ||
|
811b3d5934 | ||
|
cac702415f | ||
|
dbe7acdc80 | ||
|
b33985b338 | ||
|
670376336e | ||
|
c59eddb00a | ||
|
fe40ba497b | ||
|
c5b7e3d8be | ||
|
47e778a0b8 | ||
|
8b443e0957 | ||
|
f7fb015bd8 | ||
|
0d7cd357c3 | ||
|
e40f65836f | ||
|
2be56f6a70 | ||
|
1ff1f3fad3 | ||
|
0ae96dfff4 | ||
|
8ad6488bd9 | ||
|
e264b68b7e | ||
|
9e881534ec | ||
|
2832ff5c76 | ||
|
4c6cca0864 | ||
|
c06bbf0b9b | ||
|
69392a4a51 | ||
|
130f1a167e | ||
|
8ab710817d | ||
|
ca39e75434 | ||
|
265b25a4c6 | ||
|
54f6e0b5c6 | ||
|
f2cdefaeec | ||
|
2d588d87ac | ||
|
5ee2eb1aa2 | ||
|
ff5f66a75f | ||
|
bf72638600 | ||
|
d9bc4da6f1 | ||
|
7f8d5ec11a | ||
|
141d0ede2d | ||
|
ab78a79415 | ||
|
8fa6af9ba4 | ||
|
f0a2845637 | ||
|
8ffc88ba28 | ||
|
05d132a1bb | ||
|
bd7c4fc4eb | ||
|
45c84d4936 | ||
|
8e8e2e0dfe |
@@ -3,7 +3,62 @@ name: Release Infisical Core Helm chart
|
|||||||
on: [workflow_dispatch]
|
on: [workflow_dispatch]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
test-helm:
|
||||||
|
name: Test Helm Chart
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Helm
|
||||||
|
uses: azure/setup-helm@v4.2.0
|
||||||
|
with:
|
||||||
|
version: v3.17.0
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5.3.0
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Add Helm repositories
|
||||||
|
run: |
|
||||||
|
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
|
||||||
|
helm repo add bitnami https://charts.bitnami.com/bitnami
|
||||||
|
helm repo update
|
||||||
|
|
||||||
|
- name: Set up chart-testing
|
||||||
|
uses: helm/chart-testing-action@v2.7.0
|
||||||
|
|
||||||
|
- name: Run chart-testing (lint)
|
||||||
|
run: ct lint --config ct.yaml --charts helm-charts/infisical-standalone-postgres
|
||||||
|
|
||||||
|
- name: Create kind cluster
|
||||||
|
uses: helm/kind-action@v1.12.0
|
||||||
|
|
||||||
|
- name: Create namespace
|
||||||
|
run: kubectl create namespace infisical-standalone-postgres
|
||||||
|
|
||||||
|
- name: Create Infisical secrets
|
||||||
|
run: |
|
||||||
|
kubectl create secret generic infisical-secrets \
|
||||||
|
--namespace infisical-standalone-postgres \
|
||||||
|
--from-literal=AUTH_SECRET=6c1fe4e407b8911c104518103505b218 \
|
||||||
|
--from-literal=ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218 \
|
||||||
|
--from-literal=SITE_URL=http://localhost:8080
|
||||||
|
|
||||||
|
- name: Run chart-testing (install)
|
||||||
|
run: |
|
||||||
|
ct install \
|
||||||
|
--config ct.yaml \
|
||||||
|
--charts helm-charts/infisical-standalone-postgres \
|
||||||
|
--helm-extra-args="--timeout=300s" \
|
||||||
|
--helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.autoDatabaseSchemaMigration=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.132.2-postgres" \
|
||||||
|
--namespace infisical-standalone-postgres
|
||||||
|
|
||||||
release:
|
release:
|
||||||
|
needs: test-helm
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
32
.github/workflows/release-k8-operator-helm.yml
vendored
32
.github/workflows/release-k8-operator-helm.yml
vendored
@@ -3,8 +3,40 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
test-helm:
|
||||||
|
name: Test Helm Chart
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Helm
|
||||||
|
uses: azure/setup-helm@v4.2.0
|
||||||
|
with:
|
||||||
|
version: v3.17.0
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5.3.0
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Set up chart-testing
|
||||||
|
uses: helm/chart-testing-action@v2.7.0
|
||||||
|
|
||||||
|
- name: Run chart-testing (lint)
|
||||||
|
run: ct lint --config ct.yaml --charts helm-charts/secrets-operator
|
||||||
|
|
||||||
|
- name: Create kind cluster
|
||||||
|
uses: helm/kind-action@v1.12.0
|
||||||
|
|
||||||
|
- name: Run chart-testing (install)
|
||||||
|
run: ct install --config ct.yaml --charts helm-charts/secrets-operator
|
||||||
|
|
||||||
release-helm:
|
release-helm:
|
||||||
name: Release Helm Chart
|
name: Release Helm Chart
|
||||||
|
needs: test-helm
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
43
.github/workflows/release_helm_gateway.yaml
vendored
43
.github/workflows/release_helm_gateway.yaml
vendored
@@ -3,8 +3,51 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
test-helm:
|
||||||
|
name: Test Helm Chart
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Helm
|
||||||
|
uses: azure/setup-helm@v4.2.0
|
||||||
|
with:
|
||||||
|
version: v3.17.0
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5.3.0
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Set up chart-testing
|
||||||
|
uses: helm/chart-testing-action@v2.7.0
|
||||||
|
|
||||||
|
- name: Run chart-testing (lint)
|
||||||
|
run: ct lint --config ct.yaml --charts helm-charts/infisical-gateway
|
||||||
|
|
||||||
|
- name: Create kind cluster
|
||||||
|
uses: helm/kind-action@v1.12.0
|
||||||
|
|
||||||
|
- name: Create namespace
|
||||||
|
run: kubectl create namespace infisical-gateway
|
||||||
|
|
||||||
|
- name: Create gateway secret
|
||||||
|
run: kubectl create secret generic infisical-gateway-environment --from-literal=TOKEN=my-test-token -n infisical-gateway
|
||||||
|
|
||||||
|
- name: Run chart-testing (install)
|
||||||
|
run: |
|
||||||
|
ct install \
|
||||||
|
--config ct.yaml \
|
||||||
|
--charts helm-charts/infisical-gateway \
|
||||||
|
--helm-extra-args="--timeout=300s" \
|
||||||
|
--namespace infisical-gateway
|
||||||
|
|
||||||
release-helm:
|
release-helm:
|
||||||
name: Release Helm Chart
|
name: Release Helm Chart
|
||||||
|
needs: test-helm
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
49
.github/workflows/run-helm-chart-tests-infisical-gateway.yml
vendored
Normal file
49
.github/workflows/run-helm-chart-tests-infisical-gateway.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
name: Run Helm Chart Tests for Gateway
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "helm-charts/infisical-gateway/**"
|
||||||
|
- ".github/workflows/run-helm-chart-tests-infisical-gateway.yml"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-helm:
|
||||||
|
name: Test Helm Chart
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Helm
|
||||||
|
uses: azure/setup-helm@v4.2.0
|
||||||
|
with:
|
||||||
|
version: v3.17.0
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5.3.0
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Set up chart-testing
|
||||||
|
uses: helm/chart-testing-action@v2.7.0
|
||||||
|
|
||||||
|
- name: Run chart-testing (lint)
|
||||||
|
run: ct lint --config ct.yaml --charts helm-charts/infisical-gateway
|
||||||
|
|
||||||
|
- name: Create kind cluster
|
||||||
|
uses: helm/kind-action@v1.12.0
|
||||||
|
|
||||||
|
- name: Create namespace
|
||||||
|
run: kubectl create namespace infisical-gateway
|
||||||
|
|
||||||
|
- name: Create gateway secret
|
||||||
|
run: kubectl create secret generic infisical-gateway-environment --from-literal=TOKEN=my-test-token -n infisical-gateway
|
||||||
|
|
||||||
|
- name: Run chart-testing (install)
|
||||||
|
run: |
|
||||||
|
ct install \
|
||||||
|
--config ct.yaml \
|
||||||
|
--charts helm-charts/infisical-gateway \
|
||||||
|
--helm-extra-args="--timeout=300s" \
|
||||||
|
--namespace infisical-gateway
|
61
.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml
vendored
Normal file
61
.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: Run Helm Chart Tests for Infisical Standalone Postgres
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "helm-charts/infisical-standalone-postgres/**"
|
||||||
|
- ".github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-helm:
|
||||||
|
name: Test Helm Chart
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Helm
|
||||||
|
uses: azure/setup-helm@v4.2.0
|
||||||
|
with:
|
||||||
|
version: v3.17.0
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5.3.0
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Add Helm repositories
|
||||||
|
run: |
|
||||||
|
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
|
||||||
|
helm repo add bitnami https://charts.bitnami.com/bitnami
|
||||||
|
helm repo update
|
||||||
|
|
||||||
|
- name: Set up chart-testing
|
||||||
|
uses: helm/chart-testing-action@v2.7.0
|
||||||
|
|
||||||
|
- name: Run chart-testing (lint)
|
||||||
|
run: ct lint --config ct.yaml --charts helm-charts/infisical-standalone-postgres
|
||||||
|
|
||||||
|
- name: Create kind cluster
|
||||||
|
uses: helm/kind-action@v1.12.0
|
||||||
|
|
||||||
|
- name: Create namespace
|
||||||
|
run: kubectl create namespace infisical-standalone-postgres
|
||||||
|
|
||||||
|
- name: Create Infisical secrets
|
||||||
|
run: |
|
||||||
|
kubectl create secret generic infisical-secrets \
|
||||||
|
--namespace infisical-standalone-postgres \
|
||||||
|
--from-literal=AUTH_SECRET=6c1fe4e407b8911c104518103505b218 \
|
||||||
|
--from-literal=ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218 \
|
||||||
|
--from-literal=SITE_URL=http://localhost:8080
|
||||||
|
|
||||||
|
- name: Run chart-testing (install)
|
||||||
|
run: |
|
||||||
|
ct install \
|
||||||
|
--config ct.yaml \
|
||||||
|
--charts helm-charts/infisical-standalone-postgres \
|
||||||
|
--helm-extra-args="--timeout=300s" \
|
||||||
|
--helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.autoDatabaseSchemaMigration=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.132.2-postgres" \
|
||||||
|
--namespace infisical-standalone-postgres
|
38
.github/workflows/run-helm-chart-tests-secret-operator.yml
vendored
Normal file
38
.github/workflows/run-helm-chart-tests-secret-operator.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: Run Helm Chart Tests for Secret Operator
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "helm-charts/secrets-operator/**"
|
||||||
|
- ".github/workflows/run-helm-chart-tests-secret-operator.yml"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-helm:
|
||||||
|
name: Test Helm Chart
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Helm
|
||||||
|
uses: azure/setup-helm@v4.2.0
|
||||||
|
with:
|
||||||
|
version: v3.17.0
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5.3.0
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Set up chart-testing
|
||||||
|
uses: helm/chart-testing-action@v2.7.0
|
||||||
|
|
||||||
|
- name: Run chart-testing (lint)
|
||||||
|
run: ct lint --config ct.yaml --charts helm-charts/secrets-operator
|
||||||
|
|
||||||
|
- name: Create kind cluster
|
||||||
|
uses: helm/kind-action@v1.12.0
|
||||||
|
|
||||||
|
- name: Run chart-testing (install)
|
||||||
|
run: ct install --config ct.yaml --charts helm-charts/secrets-operator
|
@@ -40,4 +40,8 @@ cli/detect/config/gitleaks.toml:gcp-api-key:578
|
|||||||
cli/detect/config/gitleaks.toml:gcp-api-key:579
|
cli/detect/config/gitleaks.toml:gcp-api-key:579
|
||||||
cli/detect/config/gitleaks.toml:gcp-api-key:581
|
cli/detect/config/gitleaks.toml:gcp-api-key:581
|
||||||
cli/detect/config/gitleaks.toml:gcp-api-key:582
|
cli/detect/config/gitleaks.toml:gcp-api-key:582
|
||||||
|
.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml:generic-api-key:51
|
||||||
|
.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml:generic-api-key:50
|
||||||
|
.github/workflows/helm-release-infisical-core.yml:generic-api-key:48
|
||||||
|
.github/workflows/helm-release-infisical-core.yml:generic-api-key:47
|
||||||
backend/src/services/smtp/smtp-service.ts:generic-api-key:79
|
backend/src/services/smtp/smtp-service.ts:generic-api-key:79
|
||||||
|
2
backend/src/@types/fastify.d.ts
vendored
2
backend/src/@types/fastify.d.ts
vendored
@@ -65,6 +65,7 @@ import { TGroupProjectServiceFactory } from "@app/services/group-project/group-p
|
|||||||
import { THsmServiceFactory } from "@app/services/hsm/hsm-service";
|
import { THsmServiceFactory } from "@app/services/hsm/hsm-service";
|
||||||
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
||||||
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
||||||
|
import { TIdentityAliCloudAuthServiceFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-service";
|
||||||
import { TIdentityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
|
import { TIdentityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
|
||||||
import { TIdentityAzureAuthServiceFactory } from "@app/services/identity-azure-auth/identity-azure-auth-service";
|
import { TIdentityAzureAuthServiceFactory } from "@app/services/identity-azure-auth/identity-azure-auth-service";
|
||||||
import { TIdentityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
|
import { TIdentityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
|
||||||
@@ -218,6 +219,7 @@ declare module "fastify" {
|
|||||||
identityUa: TIdentityUaServiceFactory;
|
identityUa: TIdentityUaServiceFactory;
|
||||||
identityKubernetesAuth: TIdentityKubernetesAuthServiceFactory;
|
identityKubernetesAuth: TIdentityKubernetesAuthServiceFactory;
|
||||||
identityGcpAuth: TIdentityGcpAuthServiceFactory;
|
identityGcpAuth: TIdentityGcpAuthServiceFactory;
|
||||||
|
identityAliCloudAuth: TIdentityAliCloudAuthServiceFactory;
|
||||||
identityAwsAuth: TIdentityAwsAuthServiceFactory;
|
identityAwsAuth: TIdentityAwsAuthServiceFactory;
|
||||||
identityAzureAuth: TIdentityAzureAuthServiceFactory;
|
identityAzureAuth: TIdentityAzureAuthServiceFactory;
|
||||||
identityOciAuth: TIdentityOciAuthServiceFactory;
|
identityOciAuth: TIdentityOciAuthServiceFactory;
|
||||||
|
8
backend/src/@types/knex.d.ts
vendored
8
backend/src/@types/knex.d.ts
vendored
@@ -125,6 +125,9 @@ import {
|
|||||||
TIdentityAccessTokens,
|
TIdentityAccessTokens,
|
||||||
TIdentityAccessTokensInsert,
|
TIdentityAccessTokensInsert,
|
||||||
TIdentityAccessTokensUpdate,
|
TIdentityAccessTokensUpdate,
|
||||||
|
TIdentityAlicloudAuths,
|
||||||
|
TIdentityAlicloudAuthsInsert,
|
||||||
|
TIdentityAlicloudAuthsUpdate,
|
||||||
TIdentityAwsAuths,
|
TIdentityAwsAuths,
|
||||||
TIdentityAwsAuthsInsert,
|
TIdentityAwsAuthsInsert,
|
||||||
TIdentityAwsAuthsUpdate,
|
TIdentityAwsAuthsUpdate,
|
||||||
@@ -786,6 +789,11 @@ declare module "knex/types/tables" {
|
|||||||
TIdentityGcpAuthsInsert,
|
TIdentityGcpAuthsInsert,
|
||||||
TIdentityGcpAuthsUpdate
|
TIdentityGcpAuthsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.IdentityAliCloudAuth]: KnexOriginal.CompositeTableType<
|
||||||
|
TIdentityAlicloudAuths,
|
||||||
|
TIdentityAlicloudAuthsInsert,
|
||||||
|
TIdentityAlicloudAuthsUpdate
|
||||||
|
>;
|
||||||
[TableName.IdentityAwsAuth]: KnexOriginal.CompositeTableType<
|
[TableName.IdentityAwsAuth]: KnexOriginal.CompositeTableType<
|
||||||
TIdentityAwsAuths,
|
TIdentityAwsAuths,
|
||||||
TIdentityAwsAuthsInsert,
|
TIdentityAwsAuthsInsert,
|
||||||
|
@@ -0,0 +1,21 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasConfigColumn = await knex.schema.hasColumn(TableName.DynamicSecretLease, "config");
|
||||||
|
if (!hasConfigColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.DynamicSecretLease, (table) => {
|
||||||
|
table.jsonb("config");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasConfigColumn = await knex.schema.hasColumn(TableName.DynamicSecretLease, "config");
|
||||||
|
if (hasConfigColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.DynamicSecretLease, (table) => {
|
||||||
|
table.dropColumn("config");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,45 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { selectAllTableCols } from "@app/lib/knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
const BATCH_SIZE = 1000;
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasKubernetesHostColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "kubernetesHost");
|
||||||
|
|
||||||
|
if (hasKubernetesHostColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (table) => {
|
||||||
|
table.string("kubernetesHost").nullable().alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasKubernetesHostColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "kubernetesHost");
|
||||||
|
|
||||||
|
// find all rows where kubernetesHost is null
|
||||||
|
const rows = await knex(TableName.IdentityKubernetesAuth)
|
||||||
|
.whereNull("kubernetesHost")
|
||||||
|
.select(selectAllTableCols(TableName.IdentityKubernetesAuth));
|
||||||
|
|
||||||
|
if (rows.length > 0) {
|
||||||
|
for (let i = 0; i < rows.length; i += BATCH_SIZE) {
|
||||||
|
const batch = rows.slice(i, i + BATCH_SIZE);
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await knex(TableName.IdentityKubernetesAuth)
|
||||||
|
.whereIn(
|
||||||
|
"id",
|
||||||
|
batch.map((row) => row.id)
|
||||||
|
)
|
||||||
|
.update({ kubernetesHost: "" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasKubernetesHostColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (table) => {
|
||||||
|
table.string("kubernetesHost").notNullable().alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,29 @@
|
|||||||
|
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.IdentityAliCloudAuth))) {
|
||||||
|
await knex.schema.createTable(TableName.IdentityAliCloudAuth, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.bigInteger("accessTokenTTL").defaultTo(7200).notNullable();
|
||||||
|
t.bigInteger("accessTokenMaxTTL").defaultTo(7200).notNullable();
|
||||||
|
t.bigInteger("accessTokenNumUsesLimit").defaultTo(0).notNullable();
|
||||||
|
t.jsonb("accessTokenTrustedIps").notNullable();
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
t.uuid("identityId").notNullable().unique();
|
||||||
|
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
|
||||||
|
t.string("type").notNullable();
|
||||||
|
|
||||||
|
t.string("allowedArns").notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.IdentityAliCloudAuth);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.IdentityAliCloudAuth);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.IdentityAliCloudAuth);
|
||||||
|
}
|
@@ -16,7 +16,8 @@ export const DynamicSecretLeasesSchema = z.object({
|
|||||||
statusDetails: z.string().nullable().optional(),
|
statusDetails: z.string().nullable().optional(),
|
||||||
dynamicSecretId: z.string().uuid(),
|
dynamicSecretId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
config: z.unknown().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TDynamicSecretLeases = z.infer<typeof DynamicSecretLeasesSchema>;
|
export type TDynamicSecretLeases = z.infer<typeof DynamicSecretLeasesSchema>;
|
||||||
|
25
backend/src/db/schemas/identity-alicloud-auths.ts
Normal file
25
backend/src/db/schemas/identity-alicloud-auths.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// 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 IdentityAlicloudAuthsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
accessTokenTTL: z.coerce.number().default(7200),
|
||||||
|
accessTokenMaxTTL: z.coerce.number().default(7200),
|
||||||
|
accessTokenNumUsesLimit: z.coerce.number().default(0),
|
||||||
|
accessTokenTrustedIps: z.unknown(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
identityId: z.string().uuid(),
|
||||||
|
type: z.string(),
|
||||||
|
allowedArns: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TIdentityAlicloudAuths = z.infer<typeof IdentityAlicloudAuthsSchema>;
|
||||||
|
export type TIdentityAlicloudAuthsInsert = Omit<z.input<typeof IdentityAlicloudAuthsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TIdentityAlicloudAuthsUpdate = Partial<Omit<z.input<typeof IdentityAlicloudAuthsSchema>, TImmutableDBKeys>>;
|
@@ -18,7 +18,7 @@ export const IdentityKubernetesAuthsSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
identityId: z.string().uuid(),
|
identityId: z.string().uuid(),
|
||||||
kubernetesHost: z.string(),
|
kubernetesHost: z.string().nullable().optional(),
|
||||||
encryptedCaCert: z.string().nullable().optional(),
|
encryptedCaCert: z.string().nullable().optional(),
|
||||||
caCertIV: z.string().nullable().optional(),
|
caCertIV: z.string().nullable().optional(),
|
||||||
caCertTag: z.string().nullable().optional(),
|
caCertTag: z.string().nullable().optional(),
|
||||||
|
@@ -39,6 +39,7 @@ export * from "./group-project-memberships";
|
|||||||
export * from "./groups";
|
export * from "./groups";
|
||||||
export * from "./identities";
|
export * from "./identities";
|
||||||
export * from "./identity-access-tokens";
|
export * from "./identity-access-tokens";
|
||||||
|
export * from "./identity-alicloud-auths";
|
||||||
export * from "./identity-aws-auths";
|
export * from "./identity-aws-auths";
|
||||||
export * from "./identity-azure-auths";
|
export * from "./identity-azure-auths";
|
||||||
export * from "./identity-gcp-auths";
|
export * from "./identity-gcp-auths";
|
||||||
|
@@ -80,6 +80,7 @@ export enum TableName {
|
|||||||
IdentityGcpAuth = "identity_gcp_auths",
|
IdentityGcpAuth = "identity_gcp_auths",
|
||||||
IdentityAzureAuth = "identity_azure_auths",
|
IdentityAzureAuth = "identity_azure_auths",
|
||||||
IdentityUaClientSecret = "identity_ua_client_secrets",
|
IdentityUaClientSecret = "identity_ua_client_secrets",
|
||||||
|
IdentityAliCloudAuth = "identity_alicloud_auths",
|
||||||
IdentityAwsAuth = "identity_aws_auths",
|
IdentityAwsAuth = "identity_aws_auths",
|
||||||
IdentityOciAuth = "identity_oci_auths",
|
IdentityOciAuth = "identity_oci_auths",
|
||||||
IdentityOidcAuth = "identity_oidc_auths",
|
IdentityOidcAuth = "identity_oidc_auths",
|
||||||
@@ -247,6 +248,7 @@ export enum IdentityAuthMethod {
|
|||||||
UNIVERSAL_AUTH = "universal-auth",
|
UNIVERSAL_AUTH = "universal-auth",
|
||||||
KUBERNETES_AUTH = "kubernetes-auth",
|
KUBERNETES_AUTH = "kubernetes-auth",
|
||||||
GCP_AUTH = "gcp-auth",
|
GCP_AUTH = "gcp-auth",
|
||||||
|
ALICLOUD_AUTH = "alicloud-auth",
|
||||||
AWS_AUTH = "aws-auth",
|
AWS_AUTH = "aws-auth",
|
||||||
AZURE_AUTH = "azure-auth",
|
AZURE_AUTH = "azure-auth",
|
||||||
OCI_AUTH = "oci-auth",
|
OCI_AUTH = "oci-auth",
|
||||||
|
@@ -0,0 +1,17 @@
|
|||||||
|
import {
|
||||||
|
CreateOracleDBConnectionSchema,
|
||||||
|
SanitizedOracleDBConnectionSchema,
|
||||||
|
UpdateOracleDBConnectionSchema
|
||||||
|
} from "@app/ee/services/app-connections/oracledb";
|
||||||
|
import { registerAppConnectionEndpoints } from "@app/server/routes/v1/app-connection-routers/app-connection-endpoints";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
export const registerOracleDBConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.OracleDB,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedOracleDBConnectionSchema,
|
||||||
|
createSchema: CreateOracleDBConnectionSchema,
|
||||||
|
updateSchema: UpdateOracleDBConnectionSchema
|
||||||
|
});
|
||||||
|
};
|
@@ -36,7 +36,8 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide
|
|||||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||||
}),
|
}),
|
||||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRET_LEASES.CREATE.path),
|
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRET_LEASES.CREATE.path),
|
||||||
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.path)
|
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.environmentSlug),
|
||||||
|
config: z.any().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@@ -0,0 +1,67 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { DynamicSecretLeasesSchema } from "@app/db/schemas";
|
||||||
|
import { ApiDocsTags, DYNAMIC_SECRET_LEASES } from "@app/lib/api-docs";
|
||||||
|
import { daysToMillisecond } from "@app/lib/dates";
|
||||||
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
|
import { ms } from "@app/lib/ms";
|
||||||
|
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerKubernetesDynamicSecretLeaseRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.DynamicSecrets],
|
||||||
|
body: z.object({
|
||||||
|
dynamicSecretName: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.dynamicSecretName).toLowerCase(),
|
||||||
|
projectSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.projectSlug),
|
||||||
|
ttl: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe(DYNAMIC_SECRET_LEASES.CREATE.ttl)
|
||||||
|
.superRefine((val, ctx) => {
|
||||||
|
if (!val) return;
|
||||||
|
const valMs = ms(val);
|
||||||
|
if (valMs < 60 * 1000)
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be greater than 1min" });
|
||||||
|
if (valMs > daysToMillisecond(1))
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||||
|
}),
|
||||||
|
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRET_LEASES.CREATE.path),
|
||||||
|
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.environmentSlug),
|
||||||
|
config: z
|
||||||
|
.object({
|
||||||
|
namespace: z.string().min(1).optional().describe(DYNAMIC_SECRET_LEASES.KUBERNETES.CREATE.config.namespace)
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
lease: DynamicSecretLeasesSchema,
|
||||||
|
dynamicSecret: SanitizedDynamicSecretSchema,
|
||||||
|
data: z.unknown()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { data, lease, dynamicSecret } = await server.services.dynamicSecretLease.create({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
name: req.body.dynamicSecretName,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
return { lease, data, dynamicSecret };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -48,7 +48,9 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
|||||||
id: z.string().trim().describe(GROUPS.GET_BY_ID.id)
|
id: z.string().trim().describe(GROUPS.GET_BY_ID.id)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: GroupsSchema
|
200: GroupsSchema.extend({
|
||||||
|
customRoleSlug: z.string().nullable()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
|
@@ -6,6 +6,7 @@ import { registerAssumePrivilegeRouter } from "./assume-privilege-router";
|
|||||||
import { registerAuditLogStreamRouter } from "./audit-log-stream-router";
|
import { registerAuditLogStreamRouter } from "./audit-log-stream-router";
|
||||||
import { registerCaCrlRouter } from "./certificate-authority-crl-router";
|
import { registerCaCrlRouter } from "./certificate-authority-crl-router";
|
||||||
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
|
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
|
||||||
|
import { registerKubernetesDynamicSecretLeaseRouter } from "./dynamic-secret-lease-routers/kubernetes-lease-router";
|
||||||
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
|
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
|
||||||
import { registerExternalKmsRouter } from "./external-kms-router";
|
import { registerExternalKmsRouter } from "./external-kms-router";
|
||||||
import { registerGatewayRouter } from "./gateway-router";
|
import { registerGatewayRouter } from "./gateway-router";
|
||||||
@@ -71,6 +72,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
async (dynamicSecretRouter) => {
|
async (dynamicSecretRouter) => {
|
||||||
await dynamicSecretRouter.register(registerDynamicSecretRouter);
|
await dynamicSecretRouter.register(registerDynamicSecretRouter);
|
||||||
await dynamicSecretRouter.register(registerDynamicSecretLeaseRouter, { prefix: "/leases" });
|
await dynamicSecretRouter.register(registerDynamicSecretLeaseRouter, { prefix: "/leases" });
|
||||||
|
await dynamicSecretRouter.register(registerKubernetesDynamicSecretLeaseRouter, { prefix: "/leases/kubernetes" });
|
||||||
},
|
},
|
||||||
{ prefix: "/dynamic-secrets" }
|
{ prefix: "/dynamic-secrets" }
|
||||||
);
|
);
|
||||||
|
@@ -6,6 +6,7 @@ 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 { registerOracleDBCredentialsRotationRouter } from "./oracledb-credentials-rotation-router";
|
||||||
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
|
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
|
||||||
|
|
||||||
export * from "./secret-rotation-v2-router";
|
export * from "./secret-rotation-v2-router";
|
||||||
@@ -17,6 +18,7 @@ export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record<
|
|||||||
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
|
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
|
||||||
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
|
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
|
||||||
[SecretRotation.MySqlCredentials]: registerMySqlCredentialsRotationRouter,
|
[SecretRotation.MySqlCredentials]: registerMySqlCredentialsRotationRouter,
|
||||||
|
[SecretRotation.OracleDBCredentials]: registerOracleDBCredentialsRotationRouter,
|
||||||
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
|
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
|
||||||
[SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter,
|
[SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter,
|
||||||
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter,
|
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter,
|
||||||
|
@@ -0,0 +1,19 @@
|
|||||||
|
import {
|
||||||
|
CreateOracleDBCredentialsRotationSchema,
|
||||||
|
OracleDBCredentialsRotationSchema,
|
||||||
|
UpdateOracleDBCredentialsRotationSchema
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/oracledb-credentials";
|
||||||
|
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
|
import { SqlCredentialsRotationGeneratedCredentialsSchema } from "@app/ee/services/secret-rotation-v2/shared/sql-credentials";
|
||||||
|
|
||||||
|
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
|
||||||
|
|
||||||
|
export const registerOracleDBCredentialsRotationRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSecretRotationEndpoints({
|
||||||
|
type: SecretRotation.OracleDBCredentials,
|
||||||
|
server,
|
||||||
|
responseSchema: OracleDBCredentialsRotationSchema,
|
||||||
|
createSchema: CreateOracleDBCredentialsRotationSchema,
|
||||||
|
updateSchema: UpdateOracleDBCredentialsRotationSchema,
|
||||||
|
generatedCredentialsSchema: SqlCredentialsRotationGeneratedCredentialsSchema
|
||||||
|
});
|
@@ -7,6 +7,7 @@ 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 { 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";
|
||||||
import { ApiDocsTags, SecretRotations } from "@app/lib/api-docs";
|
import { ApiDocsTags, SecretRotations } from "@app/lib/api-docs";
|
||||||
@@ -18,6 +19,7 @@ const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
|
|||||||
PostgresCredentialsRotationListItemSchema,
|
PostgresCredentialsRotationListItemSchema,
|
||||||
MsSqlCredentialsRotationListItemSchema,
|
MsSqlCredentialsRotationListItemSchema,
|
||||||
MySqlCredentialsRotationListItemSchema,
|
MySqlCredentialsRotationListItemSchema,
|
||||||
|
OracleDBCredentialsRotationListItemSchema,
|
||||||
Auth0ClientSecretRotationListItemSchema,
|
Auth0ClientSecretRotationListItemSchema,
|
||||||
AzureClientSecretRotationListItemSchema,
|
AzureClientSecretRotationListItemSchema,
|
||||||
AwsIamUserSecretRotationListItemSchema,
|
AwsIamUserSecretRotationListItemSchema,
|
||||||
|
@@ -187,6 +187,56 @@ export const registerSecretScanningV2Router = async (server: FastifyZodProvider)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/findings",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SecretScanning],
|
||||||
|
description: "Update one or more Secret Scanning Findings in a batch.",
|
||||||
|
body: z
|
||||||
|
.object({
|
||||||
|
findingId: z.string().trim().min(1, "Finding ID required").describe(SecretScanningFindings.UPDATE.findingId),
|
||||||
|
status: z.nativeEnum(SecretScanningFindingStatus).optional().describe(SecretScanningFindings.UPDATE.status),
|
||||||
|
remarks: z.string().nullish().describe(SecretScanningFindings.UPDATE.remarks)
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.max(500),
|
||||||
|
response: {
|
||||||
|
200: z.object({ findings: SecretScanningFindingSchema.array() })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { body, permission } = req;
|
||||||
|
|
||||||
|
const updatedFindingPromises = body.map(async (findingUpdatePayload) => {
|
||||||
|
const { finding, projectId } = await server.services.secretScanningV2.updateSecretScanningFindingById(
|
||||||
|
findingUpdatePayload,
|
||||||
|
permission
|
||||||
|
);
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.SECRET_SCANNING_FINDING_UPDATE,
|
||||||
|
metadata: findingUpdatePayload
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return finding;
|
||||||
|
});
|
||||||
|
|
||||||
|
const findings = await Promise.all(updatedFindingPromises);
|
||||||
|
|
||||||
|
return { findings };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/configs",
|
url: "/configs",
|
||||||
|
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./oracledb-connection-enums";
|
||||||
|
export * from "./oracledb-connection-fns";
|
||||||
|
export * from "./oracledb-connection-schemas";
|
||||||
|
export * from "./oracledb-connection-types";
|
@@ -0,0 +1,3 @@
|
|||||||
|
export enum OracleDBConnectionMethod {
|
||||||
|
UsernameAndPassword = "username-and-password"
|
||||||
|
}
|
@@ -0,0 +1,12 @@
|
|||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
import { OracleDBConnectionMethod } from "./oracledb-connection-enums";
|
||||||
|
|
||||||
|
export const getOracleDBConnectionListItem = () => {
|
||||||
|
return {
|
||||||
|
name: "OracleDB" as const,
|
||||||
|
app: AppConnection.OracleDB as const,
|
||||||
|
methods: Object.values(OracleDBConnectionMethod) as [OracleDBConnectionMethod.UsernameAndPassword],
|
||||||
|
supportsPlatformManagement: true as const
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,64 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { AppConnections } from "@app/lib/api-docs";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
BaseAppConnectionSchema,
|
||||||
|
GenericCreateAppConnectionFieldsSchema,
|
||||||
|
GenericUpdateAppConnectionFieldsSchema
|
||||||
|
} from "@app/services/app-connection/app-connection-schemas";
|
||||||
|
import { BaseSqlUsernameAndPasswordConnectionSchema } from "@app/services/app-connection/shared/sql";
|
||||||
|
|
||||||
|
import { OracleDBConnectionMethod } from "./oracledb-connection-enums";
|
||||||
|
|
||||||
|
export const OracleDBConnectionCredentialsSchema = BaseSqlUsernameAndPasswordConnectionSchema;
|
||||||
|
|
||||||
|
const BaseOracleDBConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.OracleDB) });
|
||||||
|
|
||||||
|
export const OracleDBConnectionSchema = BaseOracleDBConnectionSchema.extend({
|
||||||
|
method: z.literal(OracleDBConnectionMethod.UsernameAndPassword),
|
||||||
|
credentials: OracleDBConnectionCredentialsSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SanitizedOracleDBConnectionSchema = z.discriminatedUnion("method", [
|
||||||
|
BaseOracleDBConnectionSchema.extend({
|
||||||
|
method: z.literal(OracleDBConnectionMethod.UsernameAndPassword),
|
||||||
|
credentials: OracleDBConnectionCredentialsSchema.pick({
|
||||||
|
host: true,
|
||||||
|
database: true,
|
||||||
|
port: true,
|
||||||
|
username: true,
|
||||||
|
sslEnabled: true,
|
||||||
|
sslRejectUnauthorized: true,
|
||||||
|
sslCertificate: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const ValidateOracleDBConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||||
|
z.object({
|
||||||
|
method: z
|
||||||
|
.literal(OracleDBConnectionMethod.UsernameAndPassword)
|
||||||
|
.describe(AppConnections.CREATE(AppConnection.OracleDB).method),
|
||||||
|
credentials: OracleDBConnectionCredentialsSchema.describe(AppConnections.CREATE(AppConnection.OracleDB).credentials)
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const CreateOracleDBConnectionSchema = ValidateOracleDBConnectionCredentialsSchema.and(
|
||||||
|
GenericCreateAppConnectionFieldsSchema(AppConnection.OracleDB, { supportsPlatformManagedCredentials: true })
|
||||||
|
);
|
||||||
|
|
||||||
|
export const UpdateOracleDBConnectionSchema = z
|
||||||
|
.object({
|
||||||
|
credentials: OracleDBConnectionCredentialsSchema.optional().describe(
|
||||||
|
AppConnections.UPDATE(AppConnection.OracleDB).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.OracleDB, { supportsPlatformManagedCredentials: true }));
|
||||||
|
|
||||||
|
export const OracleDBConnectionListItemSchema = z.object({
|
||||||
|
name: z.literal("OracleDB"),
|
||||||
|
app: z.literal(AppConnection.OracleDB),
|
||||||
|
methods: z.nativeEnum(OracleDBConnectionMethod).array(),
|
||||||
|
supportsPlatformManagement: z.literal(true)
|
||||||
|
});
|
@@ -0,0 +1,17 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CreateOracleDBConnectionSchema,
|
||||||
|
OracleDBConnectionSchema,
|
||||||
|
ValidateOracleDBConnectionCredentialsSchema
|
||||||
|
} from "./oracledb-connection-schemas";
|
||||||
|
|
||||||
|
export type TOracleDBConnection = z.infer<typeof OracleDBConnectionSchema>;
|
||||||
|
|
||||||
|
export type TOracleDBConnectionInput = z.infer<typeof CreateOracleDBConnectionSchema> & {
|
||||||
|
app: AppConnection.OracleDB;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TValidateOracleDBConnectionCredentialsSchema = typeof ValidateOracleDBConnectionCredentialsSchema;
|
@@ -170,6 +170,12 @@ export enum EventType {
|
|||||||
REVOKE_IDENTITY_GCP_AUTH = "revoke-identity-gcp-auth",
|
REVOKE_IDENTITY_GCP_AUTH = "revoke-identity-gcp-auth",
|
||||||
GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth",
|
GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth",
|
||||||
|
|
||||||
|
LOGIN_IDENTITY_ALICLOUD_AUTH = "login-identity-alicloud-auth",
|
||||||
|
ADD_IDENTITY_ALICLOUD_AUTH = "add-identity-alicloud-auth",
|
||||||
|
UPDATE_IDENTITY_ALICLOUD_AUTH = "update-identity-alicloud-auth",
|
||||||
|
REVOKE_IDENTITY_ALICLOUD_AUTH = "revoke-identity-alicloud-auth",
|
||||||
|
GET_IDENTITY_ALICLOUD_AUTH = "get-identity-alicloud-auth",
|
||||||
|
|
||||||
LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
|
LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
|
||||||
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
|
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
|
||||||
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
|
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
|
||||||
@@ -1060,6 +1066,53 @@ interface GetIdentityAwsAuthEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LoginIdentityAliCloudAuthEvent {
|
||||||
|
type: EventType.LOGIN_IDENTITY_ALICLOUD_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
identityAliCloudAuthId: string;
|
||||||
|
identityAccessTokenId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddIdentityAliCloudAuthEvent {
|
||||||
|
type: EventType.ADD_IDENTITY_ALICLOUD_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
allowedArns: string;
|
||||||
|
accessTokenTTL: number;
|
||||||
|
accessTokenMaxTTL: number;
|
||||||
|
accessTokenNumUsesLimit: number;
|
||||||
|
accessTokenTrustedIps: Array<TIdentityTrustedIp>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteIdentityAliCloudAuthEvent {
|
||||||
|
type: EventType.REVOKE_IDENTITY_ALICLOUD_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateIdentityAliCloudAuthEvent {
|
||||||
|
type: EventType.UPDATE_IDENTITY_ALICLOUD_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
allowedArns: string;
|
||||||
|
accessTokenTTL?: number;
|
||||||
|
accessTokenMaxTTL?: number;
|
||||||
|
accessTokenNumUsesLimit?: number;
|
||||||
|
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetIdentityAliCloudAuthEvent {
|
||||||
|
type: EventType.GET_IDENTITY_ALICLOUD_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface LoginIdentityOciAuthEvent {
|
interface LoginIdentityOciAuthEvent {
|
||||||
type: EventType.LOGIN_IDENTITY_OCI_AUTH;
|
type: EventType.LOGIN_IDENTITY_OCI_AUTH;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -3272,6 +3325,11 @@ export type Event =
|
|||||||
| UpdateIdentityAwsAuthEvent
|
| UpdateIdentityAwsAuthEvent
|
||||||
| GetIdentityAwsAuthEvent
|
| GetIdentityAwsAuthEvent
|
||||||
| DeleteIdentityAwsAuthEvent
|
| DeleteIdentityAwsAuthEvent
|
||||||
|
| LoginIdentityAliCloudAuthEvent
|
||||||
|
| AddIdentityAliCloudAuthEvent
|
||||||
|
| UpdateIdentityAliCloudAuthEvent
|
||||||
|
| GetIdentityAliCloudAuthEvent
|
||||||
|
| DeleteIdentityAliCloudAuthEvent
|
||||||
| LoginIdentityOciAuthEvent
|
| LoginIdentityOciAuthEvent
|
||||||
| AddIdentityOciAuthEvent
|
| AddIdentityOciAuthEvent
|
||||||
| UpdateIdentityOciAuthEvent
|
| UpdateIdentityOciAuthEvent
|
||||||
|
@@ -10,6 +10,7 @@ import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal";
|
|||||||
import { DynamicSecretStatus } from "../dynamic-secret/dynamic-secret-types";
|
import { DynamicSecretStatus } from "../dynamic-secret/dynamic-secret-types";
|
||||||
import { DynamicSecretProviders, TDynamicProviderFns } from "../dynamic-secret/providers/models";
|
import { DynamicSecretProviders, TDynamicProviderFns } from "../dynamic-secret/providers/models";
|
||||||
import { TDynamicSecretLeaseDALFactory } from "./dynamic-secret-lease-dal";
|
import { TDynamicSecretLeaseDALFactory } from "./dynamic-secret-lease-dal";
|
||||||
|
import { TDynamicSecretLeaseConfig } from "./dynamic-secret-lease-types";
|
||||||
|
|
||||||
type TDynamicSecretLeaseQueueServiceFactoryDep = {
|
type TDynamicSecretLeaseQueueServiceFactoryDep = {
|
||||||
queueService: TQueueServiceFactory;
|
queueService: TQueueServiceFactory;
|
||||||
@@ -134,10 +135,15 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
|
|||||||
|
|
||||||
await Promise.all(dynamicSecretLeases.map(({ id }) => unsetLeaseRevocation(id)));
|
await Promise.all(dynamicSecretLeases.map(({ id }) => unsetLeaseRevocation(id)));
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
dynamicSecretLeases.map(({ externalEntityId }) =>
|
dynamicSecretLeases.map(({ externalEntityId, config }) =>
|
||||||
selectedProvider.revoke(decryptedStoredInput, externalEntityId, {
|
selectedProvider.revoke(
|
||||||
|
decryptedStoredInput,
|
||||||
|
externalEntityId,
|
||||||
|
{
|
||||||
projectId: folder.projectId
|
projectId: folder.projectId
|
||||||
})
|
},
|
||||||
|
config as TDynamicSecretLeaseConfig
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -29,6 +29,7 @@ import {
|
|||||||
TCreateDynamicSecretLeaseDTO,
|
TCreateDynamicSecretLeaseDTO,
|
||||||
TDeleteDynamicSecretLeaseDTO,
|
TDeleteDynamicSecretLeaseDTO,
|
||||||
TDetailsDynamicSecretLeaseDTO,
|
TDetailsDynamicSecretLeaseDTO,
|
||||||
|
TDynamicSecretLeaseConfig,
|
||||||
TListDynamicSecretLeasesDTO,
|
TListDynamicSecretLeasesDTO,
|
||||||
TRenewDynamicSecretLeaseDTO
|
TRenewDynamicSecretLeaseDTO
|
||||||
} from "./dynamic-secret-lease-types";
|
} from "./dynamic-secret-lease-types";
|
||||||
@@ -77,7 +78,8 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
ttl
|
ttl,
|
||||||
|
config
|
||||||
}: TCreateDynamicSecretLeaseDTO) => {
|
}: TCreateDynamicSecretLeaseDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
@@ -163,7 +165,8 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
expireAt: expireAt.getTime(),
|
expireAt: expireAt.getTime(),
|
||||||
usernameTemplate: dynamicSecretCfg.usernameTemplate,
|
usernameTemplate: dynamicSecretCfg.usernameTemplate,
|
||||||
identity,
|
identity,
|
||||||
metadata: { projectId }
|
metadata: { projectId },
|
||||||
|
config
|
||||||
});
|
});
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error && typeof error === "object" && error !== null && "sqlMessage" in error) {
|
if (error && typeof error === "object" && error !== null && "sqlMessage" in error) {
|
||||||
@@ -177,8 +180,10 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
expireAt,
|
expireAt,
|
||||||
version: 1,
|
version: 1,
|
||||||
dynamicSecretId: dynamicSecretCfg.id,
|
dynamicSecretId: dynamicSecretCfg.id,
|
||||||
externalEntityId: entityId
|
externalEntityId: entityId,
|
||||||
|
config
|
||||||
});
|
});
|
||||||
|
|
||||||
await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, Number(expireAt) - Number(new Date()));
|
await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, Number(expireAt) - Number(new Date()));
|
||||||
return { lease: dynamicSecretLease, dynamicSecret: dynamicSecretCfg, data };
|
return { lease: dynamicSecretLease, dynamicSecret: dynamicSecretCfg, data };
|
||||||
};
|
};
|
||||||
@@ -259,7 +264,10 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
const expireAt = new Date(dynamicSecretLease.expireAt.getTime() + ms(selectedTTL));
|
const expireAt = new Date(dynamicSecretLease.expireAt.getTime() + ms(selectedTTL));
|
||||||
if (maxTTL) {
|
if (maxTTL) {
|
||||||
const maxExpiryDate = new Date(dynamicSecretLease.createdAt.getTime() + ms(maxTTL));
|
const maxExpiryDate = new Date(dynamicSecretLease.createdAt.getTime() + ms(maxTTL));
|
||||||
if (expireAt > maxExpiryDate) throw new BadRequestError({ message: "TTL cannot be larger than max ttl" });
|
if (expireAt > maxExpiryDate)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The requested renewal would exceed the maximum allowed lease duration. Please choose a shorter TTL"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { entityId } = await selectedProvider.renew(
|
const { entityId } = await selectedProvider.renew(
|
||||||
@@ -342,7 +350,12 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
) as object;
|
) as object;
|
||||||
|
|
||||||
const revokeResponse = await selectedProvider
|
const revokeResponse = await selectedProvider
|
||||||
.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId, { projectId })
|
.revoke(
|
||||||
|
decryptedStoredInput,
|
||||||
|
dynamicSecretLease.externalEntityId,
|
||||||
|
{ projectId },
|
||||||
|
dynamicSecretLease.config as TDynamicSecretLeaseConfig
|
||||||
|
)
|
||||||
.catch(async (err) => {
|
.catch(async (err) => {
|
||||||
// only propogate this error if forced is false
|
// only propogate this error if forced is false
|
||||||
if (!isForced) return { error: err as Error };
|
if (!isForced) return { error: err as Error };
|
||||||
|
@@ -10,6 +10,7 @@ export type TCreateDynamicSecretLeaseDTO = {
|
|||||||
environmentSlug: string;
|
environmentSlug: string;
|
||||||
ttl?: string;
|
ttl?: string;
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
|
config?: TDynamicSecretLeaseConfig;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TDetailsDynamicSecretLeaseDTO = {
|
export type TDetailsDynamicSecretLeaseDTO = {
|
||||||
@@ -41,3 +42,9 @@ export type TRenewDynamicSecretLeaseDTO = {
|
|||||||
ttl?: string;
|
ttl?: string;
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TDynamicSecretKubernetesLeaseConfig = {
|
||||||
|
namespace?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDynamicSecretLeaseConfig = TDynamicSecretKubernetesLeaseConfig;
|
||||||
|
105
backend/src/ee/services/dynamic-secret/providers/gcp-iam.ts
Normal file
105
backend/src/ee/services/dynamic-secret/providers/gcp-iam.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { gaxios, Impersonated, JWT } from "google-auth-library";
|
||||||
|
import { GetAccessTokenResponse } from "google-auth-library/build/src/auth/oauth2client";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
|
import { DynamicSecretGcpIamSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
|
export const GcpIamProvider = (): TDynamicProviderFns => {
|
||||||
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
|
const providerInputs = await DynamicSecretGcpIamSchema.parseAsync(inputs);
|
||||||
|
return providerInputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
const $getToken = async (serviceAccountEmail: string, ttl: number): Promise<string> => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
if (!appCfg.INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL) {
|
||||||
|
throw new InternalServerError({
|
||||||
|
message: "Environment variable has not been configured: INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const credJson = JSON.parse(appCfg.INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL) as {
|
||||||
|
client_email: string;
|
||||||
|
private_key: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sourceClient = new JWT({
|
||||||
|
email: credJson.client_email,
|
||||||
|
key: credJson.private_key,
|
||||||
|
scopes: ["https://www.googleapis.com/auth/cloud-platform"]
|
||||||
|
});
|
||||||
|
|
||||||
|
const impersonatedCredentials = new Impersonated({
|
||||||
|
sourceClient,
|
||||||
|
targetPrincipal: serviceAccountEmail,
|
||||||
|
lifetime: ttl,
|
||||||
|
delegates: [],
|
||||||
|
targetScopes: ["https://www.googleapis.com/auth/iam", "https://www.googleapis.com/auth/cloud-platform"]
|
||||||
|
});
|
||||||
|
|
||||||
|
let tokenResponse: GetAccessTokenResponse | undefined;
|
||||||
|
try {
|
||||||
|
tokenResponse = await impersonatedCredentials.getAccessToken();
|
||||||
|
} catch (error) {
|
||||||
|
let message = "Unable to validate connection";
|
||||||
|
if (error instanceof gaxios.GaxiosError) {
|
||||||
|
message = error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({
|
||||||
|
message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tokenResponse || !tokenResponse.token) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Unable to validate connection"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenResponse.token;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateConnection = async (inputs: unknown) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
await $getToken(providerInputs.serviceAccountEmail, 10);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const create = async (data: { inputs: unknown; expireAt: number }) => {
|
||||||
|
const { inputs, expireAt } = data;
|
||||||
|
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
const ttl = Math.max(Math.floor(expireAt / 1000) - now, 0);
|
||||||
|
|
||||||
|
const token = await $getToken(providerInputs.serviceAccountEmail, ttl);
|
||||||
|
const entityId = alphaNumericNanoId(32);
|
||||||
|
|
||||||
|
return { entityId, data: { SERVICE_ACCOUNT_EMAIL: providerInputs.serviceAccountEmail, TOKEN: token } };
|
||||||
|
};
|
||||||
|
|
||||||
|
const revoke = async (_inputs: unknown, entityId: string) => {
|
||||||
|
// There's no way to revoke GCP IAM access tokens
|
||||||
|
return { entityId };
|
||||||
|
};
|
||||||
|
|
||||||
|
const renew = async (inputs: unknown, entityId: string, expireAt: number) => {
|
||||||
|
// To renew a token it must be re-created
|
||||||
|
const data = await create({ inputs, expireAt });
|
||||||
|
|
||||||
|
return { ...data, entityId };
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
validateProviderInputs,
|
||||||
|
validateConnection,
|
||||||
|
create,
|
||||||
|
revoke,
|
||||||
|
renew
|
||||||
|
};
|
||||||
|
};
|
@@ -6,6 +6,7 @@ import { AwsIamProvider } from "./aws-iam";
|
|||||||
import { AzureEntraIDProvider } from "./azure-entra-id";
|
import { AzureEntraIDProvider } from "./azure-entra-id";
|
||||||
import { CassandraProvider } from "./cassandra";
|
import { CassandraProvider } from "./cassandra";
|
||||||
import { ElasticSearchProvider } from "./elastic-search";
|
import { ElasticSearchProvider } from "./elastic-search";
|
||||||
|
import { GcpIamProvider } from "./gcp-iam";
|
||||||
import { KubernetesProvider } from "./kubernetes";
|
import { KubernetesProvider } from "./kubernetes";
|
||||||
import { LdapProvider } from "./ldap";
|
import { LdapProvider } from "./ldap";
|
||||||
import { DynamicSecretProviders, TDynamicProviderFns } from "./models";
|
import { DynamicSecretProviders, TDynamicProviderFns } from "./models";
|
||||||
@@ -42,5 +43,6 @@ export const buildDynamicSecretProviders = ({
|
|||||||
[DynamicSecretProviders.Totp]: TotpProvider(),
|
[DynamicSecretProviders.Totp]: TotpProvider(),
|
||||||
[DynamicSecretProviders.SapAse]: SapAseProvider(),
|
[DynamicSecretProviders.SapAse]: SapAseProvider(),
|
||||||
[DynamicSecretProviders.Kubernetes]: KubernetesProvider({ gatewayService }),
|
[DynamicSecretProviders.Kubernetes]: KubernetesProvider({ gatewayService }),
|
||||||
[DynamicSecretProviders.Vertica]: VerticaProvider({ gatewayService })
|
[DynamicSecretProviders.Vertica]: VerticaProvider({ gatewayService }),
|
||||||
|
[DynamicSecretProviders.GcpIam]: GcpIamProvider()
|
||||||
});
|
});
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
import axios from "axios";
|
import axios, { AxiosError } from "axios";
|
||||||
import handlebars from "handlebars";
|
import handlebars from "handlebars";
|
||||||
import https from "https";
|
import https from "https";
|
||||||
|
|
||||||
import { InternalServerError } from "@app/lib/errors";
|
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
||||||
import { GatewayHttpProxyActions, GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
|
import { GatewayHttpProxyActions, GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||||
import { TKubernetesTokenRequest } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-types";
|
import { TKubernetesTokenRequest } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-types";
|
||||||
|
|
||||||
|
import { TDynamicSecretKubernetesLeaseConfig } from "../../dynamic-secret-lease/dynamic-secret-lease-types";
|
||||||
import { TGatewayServiceFactory } from "../../gateway/gateway-service";
|
import { TGatewayServiceFactory } from "../../gateway/gateway-service";
|
||||||
import {
|
import {
|
||||||
DynamicSecretKubernetesSchema,
|
DynamicSecretKubernetesSchema,
|
||||||
@@ -19,6 +20,9 @@ import {
|
|||||||
|
|
||||||
const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
|
const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
|
||||||
|
|
||||||
|
// This value is just a placeholder. When using gateway auth method, the url is irrelevant.
|
||||||
|
const GATEWAY_AUTH_DEFAULT_URL = "https://kubernetes.default.svc.cluster.local";
|
||||||
|
|
||||||
type TKubernetesProviderDTO = {
|
type TKubernetesProviderDTO = {
|
||||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
||||||
};
|
};
|
||||||
@@ -36,7 +40,7 @@ const generateUsername = (usernameTemplate?: string | null) => {
|
|||||||
export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO): TDynamicProviderFns => {
|
export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO): TDynamicProviderFns => {
|
||||||
const validateProviderInputs = async (inputs: unknown) => {
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
const providerInputs = await DynamicSecretKubernetesSchema.parseAsync(inputs);
|
const providerInputs = await DynamicSecretKubernetesSchema.parseAsync(inputs);
|
||||||
if (!providerInputs.gatewayId) {
|
if (!providerInputs.gatewayId && providerInputs.url) {
|
||||||
await blockLocalAndPrivateIpAddresses(providerInputs.url);
|
await blockLocalAndPrivateIpAddresses(providerInputs.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,37 +107,46 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
|||||||
const serviceAccountName = generateUsername();
|
const serviceAccountName = generateUsername();
|
||||||
const roleBindingName = `${serviceAccountName}-role-binding`;
|
const roleBindingName = `${serviceAccountName}-role-binding`;
|
||||||
|
|
||||||
|
const namespaces = providerInputs.namespace.split(",").map((namespace) => namespace.trim());
|
||||||
|
|
||||||
|
// Test each namespace sequentially instead of in parallel to simplify cleanup
|
||||||
|
for await (const namespace of namespaces) {
|
||||||
|
try {
|
||||||
// 1. Create a test service account
|
// 1. Create a test service account
|
||||||
await axios.post(
|
await axios.post(
|
||||||
`${baseUrl}/api/v1/namespaces/${providerInputs.namespace}/serviceaccounts`,
|
`${baseUrl}/api/v1/namespaces/${namespace}/serviceaccounts`,
|
||||||
{
|
{
|
||||||
metadata: {
|
metadata: {
|
||||||
name: serviceAccountName,
|
name: serviceAccountName,
|
||||||
namespace: providerInputs.namespace
|
namespace
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||||
},
|
},
|
||||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
? {
|
||||||
httpsAgent
|
httpsAgent
|
||||||
}
|
}
|
||||||
|
: {}),
|
||||||
|
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||||
|
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2. Create a test role binding
|
// 2. Create a test role binding
|
||||||
const roleBindingUrl =
|
const roleBindingUrl =
|
||||||
providerInputs.roleType === KubernetesRoleType.ClusterRole
|
providerInputs.roleType === KubernetesRoleType.ClusterRole
|
||||||
? `${baseUrl}/apis/rbac.authorization.k8s.io/v1/clusterrolebindings`
|
? `${baseUrl}/apis/rbac.authorization.k8s.io/v1/clusterrolebindings`
|
||||||
: `${baseUrl}/apis/rbac.authorization.k8s.io/v1/namespaces/${providerInputs.namespace}/rolebindings`;
|
: `${baseUrl}/apis/rbac.authorization.k8s.io/v1/namespaces/${namespace}/rolebindings`;
|
||||||
|
|
||||||
const roleBindingMetadata = {
|
const roleBindingMetadata = {
|
||||||
name: roleBindingName,
|
name: roleBindingName,
|
||||||
...(providerInputs.roleType !== KubernetesRoleType.ClusterRole && { namespace: providerInputs.namespace })
|
...(providerInputs.roleType !== KubernetesRoleType.ClusterRole && { namespace })
|
||||||
};
|
};
|
||||||
|
|
||||||
await axios.post(
|
await axios.post(
|
||||||
@@ -149,7 +162,7 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
|||||||
{
|
{
|
||||||
kind: "ServiceAccount",
|
kind: "ServiceAccount",
|
||||||
name: serviceAccountName,
|
name: serviceAccountName,
|
||||||
namespace: providerInputs.namespace
|
namespace
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -157,18 +170,22 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||||
},
|
},
|
||||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
? {
|
||||||
httpsAgent
|
httpsAgent
|
||||||
}
|
}
|
||||||
|
: {}),
|
||||||
|
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||||
|
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. Request a token for the test service account
|
// 3. Request a token for the test service account
|
||||||
await axios.post(
|
await axios.post(
|
||||||
`${baseUrl}/api/v1/namespaces/${providerInputs.namespace}/serviceaccounts/${serviceAccountName}/token`,
|
`${baseUrl}/api/v1/namespaces/${namespace}/serviceaccounts/${serviceAccountName}/token`,
|
||||||
{
|
{
|
||||||
spec: {
|
spec: {
|
||||||
expirationSeconds: 600, // 10 minutes
|
expirationSeconds: 600, // 10 minutes
|
||||||
@@ -179,59 +196,84 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||||
},
|
},
|
||||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
? {
|
||||||
httpsAgent
|
httpsAgent
|
||||||
}
|
}
|
||||||
|
: {}),
|
||||||
|
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||||
|
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// 4. Cleanup: delete role binding and service account
|
// 4. Cleanup: delete role binding and service account
|
||||||
if (providerInputs.roleType === KubernetesRoleType.Role) {
|
if (providerInputs.roleType === KubernetesRoleType.Role) {
|
||||||
await axios.delete(
|
await axios.delete(
|
||||||
`${baseUrl}/apis/rbac.authorization.k8s.io/v1/namespaces/${providerInputs.namespace}/rolebindings/${roleBindingName}`,
|
`${baseUrl}/apis/rbac.authorization.k8s.io/v1/namespaces/${namespace}/rolebindings/${roleBindingName}`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||||
},
|
},
|
||||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
? {
|
||||||
httpsAgent
|
httpsAgent
|
||||||
}
|
}
|
||||||
|
: {}),
|
||||||
|
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||||
|
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await axios.delete(`${baseUrl}/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/${roleBindingName}`, {
|
await axios.delete(`${baseUrl}/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/${roleBindingName}`, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||||
},
|
},
|
||||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
? {
|
||||||
httpsAgent
|
httpsAgent
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||||
|
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await axios.delete(
|
await axios.delete(`${baseUrl}/api/v1/namespaces/${namespace}/serviceaccounts/${serviceAccountName}`, {
|
||||||
`${baseUrl}/api/v1/namespaces/${providerInputs.namespace}/serviceaccounts/${serviceAccountName}`,
|
|
||||||
{
|
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||||
},
|
},
|
||||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
? {
|
||||||
httpsAgent
|
httpsAgent
|
||||||
}
|
}
|
||||||
);
|
: {}),
|
||||||
|
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||||
|
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const cleanupInfo = `You may need to manually clean up the following resources in namespace "${namespace}": Service Account - ${serviceAccountName}, ${providerInputs.roleType === KubernetesRoleType.Role ? "Role" : "Cluster Role"} Binding - ${roleBindingName}.`;
|
||||||
|
let mainErrorMessage = "Unknown error";
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
mainErrorMessage = (error.response?.data as { message: string })?.message;
|
||||||
|
} else if (error instanceof Error) {
|
||||||
|
mainErrorMessage = error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`${mainErrorMessage}. ${cleanupInfo}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const serviceAccountStaticCallback = async (host: string, port: number, httpsAgent?: https.Agent) => {
|
const serviceAccountStaticCallback = async (host: string, port: number, httpsAgent?: https.Agent) => {
|
||||||
@@ -247,17 +289,23 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||||
},
|
},
|
||||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
? {
|
||||||
httpsAgent
|
httpsAgent
|
||||||
}
|
}
|
||||||
|
: {}),
|
||||||
|
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||||
|
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const url = new URL(providerInputs.url);
|
const rawUrl =
|
||||||
|
providerInputs.authMethod === KubernetesAuthMethod.Gateway ? GATEWAY_AUTH_DEFAULT_URL : providerInputs.url || "";
|
||||||
|
const url = new URL(rawUrl);
|
||||||
const k8sGatewayHost = url.hostname;
|
const k8sGatewayHost = url.hostname;
|
||||||
const k8sPort = url.port ? Number(url.port) : 443;
|
const k8sPort = url.port ? Number(url.port) : 443;
|
||||||
const k8sHost = `${url.protocol}//${url.hostname}`;
|
const k8sHost = `${url.protocol}//${url.hostname}`;
|
||||||
@@ -315,11 +363,13 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
|||||||
const create = async ({
|
const create = async ({
|
||||||
inputs,
|
inputs,
|
||||||
expireAt,
|
expireAt,
|
||||||
usernameTemplate
|
usernameTemplate,
|
||||||
|
config
|
||||||
}: {
|
}: {
|
||||||
inputs: unknown;
|
inputs: unknown;
|
||||||
expireAt: number;
|
expireAt: number;
|
||||||
usernameTemplate?: string | null;
|
usernameTemplate?: string | null;
|
||||||
|
config?: TDynamicSecretKubernetesLeaseConfig;
|
||||||
}) => {
|
}) => {
|
||||||
const providerInputs = await validateProviderInputs(inputs);
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
|
||||||
@@ -331,38 +381,56 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
|||||||
const baseUrl = port ? `${host}:${port}` : host;
|
const baseUrl = port ? `${host}:${port}` : host;
|
||||||
const serviceAccountName = generateUsername(usernameTemplate);
|
const serviceAccountName = generateUsername(usernameTemplate);
|
||||||
const roleBindingName = `${serviceAccountName}-role-binding`;
|
const roleBindingName = `${serviceAccountName}-role-binding`;
|
||||||
|
const allowedNamespaces = providerInputs.namespace.split(",").map((namespace) => namespace.trim());
|
||||||
|
|
||||||
|
if (config?.namespace && !allowedNamespaces?.includes(config?.namespace)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Namespace ${config?.namespace} is not allowed. Allowed namespaces: ${allowedNamespaces?.join(", ")}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const namespace = config?.namespace || allowedNamespaces[0];
|
||||||
|
if (!namespace) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "No namespace provided"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Create the service account
|
// 1. Create the service account
|
||||||
await axios.post(
|
await axios.post(
|
||||||
`${baseUrl}/api/v1/namespaces/${providerInputs.namespace}/serviceaccounts`,
|
`${baseUrl}/api/v1/namespaces/${namespace}/serviceaccounts`,
|
||||||
{
|
{
|
||||||
metadata: {
|
metadata: {
|
||||||
name: serviceAccountName,
|
name: serviceAccountName,
|
||||||
namespace: providerInputs.namespace
|
namespace
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||||
},
|
},
|
||||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
? {
|
||||||
httpsAgent
|
httpsAgent
|
||||||
}
|
}
|
||||||
|
: {}),
|
||||||
|
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||||
|
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2. Create the role binding
|
// 2. Create the role binding
|
||||||
const roleBindingUrl =
|
const roleBindingUrl =
|
||||||
providerInputs.roleType === KubernetesRoleType.ClusterRole
|
providerInputs.roleType === KubernetesRoleType.ClusterRole
|
||||||
? `${baseUrl}/apis/rbac.authorization.k8s.io/v1/clusterrolebindings`
|
? `${baseUrl}/apis/rbac.authorization.k8s.io/v1/clusterrolebindings`
|
||||||
: `${baseUrl}/apis/rbac.authorization.k8s.io/v1/namespaces/${providerInputs.namespace}/rolebindings`;
|
: `${baseUrl}/apis/rbac.authorization.k8s.io/v1/namespaces/${namespace}/rolebindings`;
|
||||||
|
|
||||||
const roleBindingMetadata = {
|
const roleBindingMetadata = {
|
||||||
name: roleBindingName,
|
name: roleBindingName,
|
||||||
...(providerInputs.roleType !== KubernetesRoleType.ClusterRole && { namespace: providerInputs.namespace })
|
...(providerInputs.roleType !== KubernetesRoleType.ClusterRole && { namespace })
|
||||||
};
|
};
|
||||||
|
|
||||||
await axios.post(
|
await axios.post(
|
||||||
@@ -378,7 +446,7 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
|||||||
{
|
{
|
||||||
kind: "ServiceAccount",
|
kind: "ServiceAccount",
|
||||||
name: serviceAccountName,
|
name: serviceAccountName,
|
||||||
namespace: providerInputs.namespace
|
namespace
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -386,18 +454,22 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||||
},
|
},
|
||||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
? {
|
||||||
httpsAgent
|
httpsAgent
|
||||||
}
|
}
|
||||||
|
: {}),
|
||||||
|
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||||
|
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. Request a token for the service account
|
// 3. Request a token for the service account
|
||||||
const res = await axios.post<TKubernetesTokenRequest>(
|
const res = await axios.post<TKubernetesTokenRequest>(
|
||||||
`${baseUrl}/api/v1/namespaces/${providerInputs.namespace}/serviceaccounts/${serviceAccountName}/token`,
|
`${baseUrl}/api/v1/namespaces/${namespace}/serviceaccounts/${serviceAccountName}/token`,
|
||||||
{
|
{
|
||||||
spec: {
|
spec: {
|
||||||
expirationSeconds: Math.floor((expireAt - Date.now()) / 1000),
|
expirationSeconds: Math.floor((expireAt - Date.now()) / 1000),
|
||||||
@@ -408,13 +480,17 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||||
},
|
},
|
||||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
? {
|
||||||
httpsAgent
|
httpsAgent
|
||||||
}
|
}
|
||||||
|
: {}),
|
||||||
|
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||||
|
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return { ...res.data, serviceAccountName };
|
return { ...res.data, serviceAccountName };
|
||||||
@@ -425,6 +501,12 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
|||||||
throw new Error("invalid callback");
|
throw new Error("invalid callback");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config?.namespace && config.namespace !== providerInputs.namespace) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Namespace ${config?.namespace} is not allowed. Allowed namespace: ${providerInputs.namespace}.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const baseUrl = port ? `${host}:${port}` : host;
|
const baseUrl = port ? `${host}:${port}` : host;
|
||||||
|
|
||||||
const res = await axios.post<TKubernetesTokenRequest>(
|
const res = await axios.post<TKubernetesTokenRequest>(
|
||||||
@@ -439,19 +521,25 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||||
},
|
},
|
||||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
? {
|
||||||
httpsAgent
|
httpsAgent
|
||||||
}
|
}
|
||||||
|
: {}),
|
||||||
|
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||||
|
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return { ...res.data, serviceAccountName: providerInputs.serviceAccountName };
|
return { ...res.data, serviceAccountName: providerInputs.serviceAccountName };
|
||||||
};
|
};
|
||||||
|
|
||||||
const url = new URL(providerInputs.url);
|
const rawUrl =
|
||||||
|
providerInputs.authMethod === KubernetesAuthMethod.Gateway ? GATEWAY_AUTH_DEFAULT_URL : providerInputs.url || "";
|
||||||
|
const url = new URL(rawUrl);
|
||||||
const k8sHost = `${url.protocol}//${url.hostname}`;
|
const k8sHost = `${url.protocol}//${url.hostname}`;
|
||||||
const k8sGatewayHost = url.hostname;
|
const k8sGatewayHost = url.hostname;
|
||||||
const k8sPort = url.port ? Number(url.port) : 443;
|
const k8sPort = url.port ? Number(url.port) : 443;
|
||||||
@@ -511,7 +599,13 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const revoke = async (inputs: unknown, entityId: string) => {
|
const revoke = async (
|
||||||
|
inputs: unknown,
|
||||||
|
entityId: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
_metadata: { projectId: string },
|
||||||
|
config?: TDynamicSecretKubernetesLeaseConfig
|
||||||
|
) => {
|
||||||
const providerInputs = await validateProviderInputs(inputs);
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
|
||||||
const serviceAccountDynamicCallback = async (host: string, port: number, httpsAgent?: https.Agent) => {
|
const serviceAccountDynamicCallback = async (host: string, port: number, httpsAgent?: https.Agent) => {
|
||||||
@@ -522,51 +616,70 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
|||||||
const baseUrl = port ? `${host}:${port}` : host;
|
const baseUrl = port ? `${host}:${port}` : host;
|
||||||
const roleBindingName = `${entityId}-role-binding`;
|
const roleBindingName = `${entityId}-role-binding`;
|
||||||
|
|
||||||
|
const namespace = config?.namespace ?? providerInputs.namespace.split(",")[0].trim();
|
||||||
|
|
||||||
if (providerInputs.roleType === KubernetesRoleType.Role) {
|
if (providerInputs.roleType === KubernetesRoleType.Role) {
|
||||||
await axios.delete(
|
await axios.delete(
|
||||||
`${baseUrl}/apis/rbac.authorization.k8s.io/v1/namespaces/${providerInputs.namespace}/rolebindings/${roleBindingName}`,
|
`${baseUrl}/apis/rbac.authorization.k8s.io/v1/namespaces/${namespace}/rolebindings/${roleBindingName}`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||||
},
|
},
|
||||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
? {
|
||||||
httpsAgent
|
httpsAgent
|
||||||
}
|
}
|
||||||
|
: {}),
|
||||||
|
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||||
|
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await axios.delete(`${baseUrl}/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/${roleBindingName}`, {
|
await axios.delete(`${baseUrl}/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/${roleBindingName}`, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||||
},
|
},
|
||||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
? {
|
||||||
httpsAgent
|
httpsAgent
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||||
|
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the service account
|
// Delete the service account
|
||||||
await axios.delete(`${baseUrl}/api/v1/namespaces/${providerInputs.namespace}/serviceaccounts/${entityId}`, {
|
await axios.delete(`${baseUrl}/api/v1/namespaces/${namespace}/serviceaccounts/${entityId}`, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||||
},
|
},
|
||||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
? {
|
||||||
httpsAgent
|
httpsAgent
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||||
|
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (providerInputs.credentialType === KubernetesCredentialType.Dynamic) {
|
if (providerInputs.credentialType === KubernetesCredentialType.Dynamic) {
|
||||||
const url = new URL(providerInputs.url);
|
const rawUrl =
|
||||||
|
providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||||
|
? GATEWAY_AUTH_DEFAULT_URL
|
||||||
|
: providerInputs.url || "";
|
||||||
|
|
||||||
|
const url = new URL(rawUrl);
|
||||||
const k8sGatewayHost = url.hostname;
|
const k8sGatewayHost = url.hostname;
|
||||||
const k8sPort = url.port ? Number(url.port) : 443;
|
const k8sPort = url.port ? Number(url.port) : 443;
|
||||||
const k8sHost = `${url.protocol}//${url.hostname}`;
|
const k8sHost = `${url.protocol}//${url.hostname}`;
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
|
import RE2 from "re2";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
|
||||||
|
|
||||||
|
import { TDynamicSecretLeaseConfig } from "../../dynamic-secret-lease/dynamic-secret-lease-types";
|
||||||
|
|
||||||
export type PasswordRequirements = {
|
export type PasswordRequirements = {
|
||||||
length: number;
|
length: number;
|
||||||
required: {
|
required: {
|
||||||
@@ -323,24 +328,54 @@ export const LdapSchema = z.union([
|
|||||||
export const DynamicSecretKubernetesSchema = z
|
export const DynamicSecretKubernetesSchema = z
|
||||||
.discriminatedUnion("credentialType", [
|
.discriminatedUnion("credentialType", [
|
||||||
z.object({
|
z.object({
|
||||||
url: z.string().url().trim().min(1),
|
url: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.refine((val: string | undefined) => !val || new RE2(/^https?:\/\/.+/).test(val), {
|
||||||
|
message: "Invalid URL. Must start with http:// or https:// (e.g. https://example.com)"
|
||||||
|
}),
|
||||||
clusterToken: z.string().trim().optional(),
|
clusterToken: z.string().trim().optional(),
|
||||||
ca: z.string().optional(),
|
ca: z.string().optional(),
|
||||||
sslEnabled: z.boolean().default(false),
|
sslEnabled: z.boolean().default(false),
|
||||||
credentialType: z.literal(KubernetesCredentialType.Static),
|
credentialType: z.literal(KubernetesCredentialType.Static),
|
||||||
serviceAccountName: z.string().trim().min(1),
|
serviceAccountName: z.string().trim().min(1),
|
||||||
namespace: z.string().trim().min(1),
|
namespace: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1)
|
||||||
|
.refine((val) => !val.includes(","), "Namespace must be a single value, not a comma-separated list")
|
||||||
|
.refine(
|
||||||
|
(val) => characterValidator([CharacterType.AlphaNumeric, CharacterType.Hyphen])(val),
|
||||||
|
"Invalid namespace format"
|
||||||
|
),
|
||||||
gatewayId: z.string().optional(),
|
gatewayId: z.string().optional(),
|
||||||
audiences: z.array(z.string().trim().min(1)),
|
audiences: z.array(z.string().trim().min(1)),
|
||||||
authMethod: z.nativeEnum(KubernetesAuthMethod).default(KubernetesAuthMethod.Api)
|
authMethod: z.nativeEnum(KubernetesAuthMethod).default(KubernetesAuthMethod.Api)
|
||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
url: z.string().url().trim().min(1),
|
url: z
|
||||||
|
.string()
|
||||||
|
.url()
|
||||||
|
.optional()
|
||||||
|
.refine((val: string | undefined) => !val || new RE2(/^https?:\/\/.+/).test(val), {
|
||||||
|
message: "Invalid URL. Must start with http:// or https:// (e.g. https://example.com)"
|
||||||
|
}),
|
||||||
clusterToken: z.string().trim().optional(),
|
clusterToken: z.string().trim().optional(),
|
||||||
ca: z.string().optional(),
|
ca: z.string().optional(),
|
||||||
sslEnabled: z.boolean().default(false),
|
sslEnabled: z.boolean().default(false),
|
||||||
credentialType: z.literal(KubernetesCredentialType.Dynamic),
|
credentialType: z.literal(KubernetesCredentialType.Dynamic),
|
||||||
namespace: z.string().trim().min(1),
|
namespace: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1)
|
||||||
|
.refine((val) => {
|
||||||
|
const namespaces = val.split(",").map((ns) => ns.trim());
|
||||||
|
return (
|
||||||
|
namespaces.length > 0 &&
|
||||||
|
namespaces.every((ns) => ns.length > 0) &&
|
||||||
|
namespaces.every((ns) => characterValidator([CharacterType.AlphaNumeric, CharacterType.Hyphen])(ns))
|
||||||
|
);
|
||||||
|
}, "Must be a valid comma-separated list of namespace values"),
|
||||||
gatewayId: z.string().optional(),
|
gatewayId: z.string().optional(),
|
||||||
audiences: z.array(z.string().trim().min(1)),
|
audiences: z.array(z.string().trim().min(1)),
|
||||||
roleType: z.nativeEnum(KubernetesRoleType),
|
roleType: z.nativeEnum(KubernetesRoleType),
|
||||||
@@ -356,13 +391,22 @@ export const DynamicSecretKubernetesSchema = z
|
|||||||
message: "When auth method is set to Gateway, a gateway must be selected"
|
message: "When auth method is set to Gateway, a gateway must be selected"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if ((data.authMethod === KubernetesAuthMethod.Api || !data.authMethod) && !data.clusterToken) {
|
if (data.authMethod === KubernetesAuthMethod.Api || !data.authMethod) {
|
||||||
|
if (!data.clusterToken) {
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
path: ["clusterToken"],
|
path: ["clusterToken"],
|
||||||
code: z.ZodIssueCode.custom,
|
code: z.ZodIssueCode.custom,
|
||||||
message: "When auth method is set to Manual Token, a cluster token must be provided"
|
message: "When auth method is set to Token, a cluster token must be provided"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (!data.url) {
|
||||||
|
ctx.addIssue({
|
||||||
|
path: ["url"],
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: "When auth method is set to Token, a cluster URL must be provided"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const DynamicSecretVerticaSchema = z.object({
|
export const DynamicSecretVerticaSchema = z.object({
|
||||||
@@ -426,6 +470,10 @@ export const DynamicSecretTotpSchema = z.discriminatedUnion("configType", [
|
|||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export const DynamicSecretGcpIamSchema = z.object({
|
||||||
|
serviceAccountEmail: z.string().email().trim().min(1, "Service account email required").max(128)
|
||||||
|
});
|
||||||
|
|
||||||
export enum DynamicSecretProviders {
|
export enum DynamicSecretProviders {
|
||||||
SqlDatabase = "sql-database",
|
SqlDatabase = "sql-database",
|
||||||
Cassandra = "cassandra",
|
Cassandra = "cassandra",
|
||||||
@@ -443,7 +491,8 @@ export enum DynamicSecretProviders {
|
|||||||
Totp = "totp",
|
Totp = "totp",
|
||||||
SapAse = "sap-ase",
|
SapAse = "sap-ase",
|
||||||
Kubernetes = "kubernetes",
|
Kubernetes = "kubernetes",
|
||||||
Vertica = "vertica"
|
Vertica = "vertica",
|
||||||
|
GcpIam = "gcp-iam"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
||||||
@@ -463,7 +512,8 @@ export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
|||||||
z.object({ type: z.literal(DynamicSecretProviders.Snowflake), inputs: DynamicSecretSnowflakeSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.Snowflake), inputs: DynamicSecretSnowflakeSchema }),
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.Totp), inputs: DynamicSecretTotpSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.Totp), inputs: DynamicSecretTotpSchema }),
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.Kubernetes), inputs: DynamicSecretKubernetesSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.Kubernetes), inputs: DynamicSecretKubernetesSchema }),
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.Vertica), inputs: DynamicSecretVerticaSchema })
|
z.object({ type: z.literal(DynamicSecretProviders.Vertica), inputs: DynamicSecretVerticaSchema }),
|
||||||
|
z.object({ type: z.literal(DynamicSecretProviders.GcpIam), inputs: DynamicSecretGcpIamSchema })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type TDynamicProviderFns = {
|
export type TDynamicProviderFns = {
|
||||||
@@ -475,10 +525,16 @@ export type TDynamicProviderFns = {
|
|||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
metadata: { projectId: string };
|
metadata: { projectId: string };
|
||||||
|
config?: TDynamicSecretLeaseConfig;
|
||||||
}) => Promise<{ entityId: string; data: unknown }>;
|
}) => Promise<{ entityId: string; data: unknown }>;
|
||||||
validateConnection: (inputs: unknown, metadata: { projectId: string }) => Promise<boolean>;
|
validateConnection: (inputs: unknown, metadata: { projectId: string }) => Promise<boolean>;
|
||||||
validateProviderInputs: (inputs: object, metadata: { projectId: string }) => Promise<unknown>;
|
validateProviderInputs: (inputs: object, metadata: { projectId: string }) => Promise<unknown>;
|
||||||
revoke: (inputs: unknown, entityId: string, metadata: { projectId: string }) => Promise<{ entityId: string }>;
|
revoke: (
|
||||||
|
inputs: unknown,
|
||||||
|
entityId: string,
|
||||||
|
metadata: { projectId: string },
|
||||||
|
config?: TDynamicSecretLeaseConfig
|
||||||
|
) => Promise<{ entityId: string }>;
|
||||||
renew: (
|
renew: (
|
||||||
inputs: unknown,
|
inputs: unknown,
|
||||||
entityId: string,
|
entityId: string,
|
||||||
|
@@ -169,11 +169,29 @@ export const groupDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const findById = async (id: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const doc = await (tx || db.replicaNode())(TableName.Groups)
|
||||||
|
.leftJoin(TableName.OrgRoles, `${TableName.Groups}.roleId`, `${TableName.OrgRoles}.id`)
|
||||||
|
.where(`${TableName.Groups}.id`, id)
|
||||||
|
.select(
|
||||||
|
selectAllTableCols(TableName.Groups),
|
||||||
|
db.ref("slug").as("customRoleSlug").withSchema(TableName.OrgRoles)
|
||||||
|
)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find by id" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
...groupOrm,
|
||||||
findGroups,
|
findGroups,
|
||||||
findByOrgId,
|
findByOrgId,
|
||||||
findAllGroupPossibleMembers,
|
findAllGroupPossibleMembers,
|
||||||
findGroupsByProjectId,
|
findGroupsByProjectId,
|
||||||
...groupOrm
|
findById
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable no-nested-ternary */
|
||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -246,7 +247,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
|
|
||||||
const { policy } = secretApprovalRequest;
|
const { policy } = secretApprovalRequest;
|
||||||
const { hasRole } = await permissionService.getProjectPermission({
|
const { hasRole, permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -262,6 +263,12 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
throw new ForbiddenRequestError({ message: "User has insufficient privileges" });
|
throw new ForbiddenRequestError({ message: "User has insufficient privileges" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasSecretReadAccess = permission.can(
|
||||||
|
ProjectPermissionSecretActions.DescribeAndReadValue,
|
||||||
|
ProjectPermissionSub.Secrets
|
||||||
|
);
|
||||||
|
const hiddenSecretValue = "******";
|
||||||
|
|
||||||
let secrets;
|
let secrets;
|
||||||
if (shouldUseSecretV2Bridge) {
|
if (shouldUseSecretV2Bridge) {
|
||||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
@@ -278,9 +285,9 @@ 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,
|
||||||
secretValue:
|
secretValue: !hasSecretReadAccess
|
||||||
// eslint-disable-next-line no-nested-ternary
|
? hiddenSecretValue
|
||||||
el.secret && el.secret.isRotatedSecret
|
: el.secret && el.secret.isRotatedSecret
|
||||||
? undefined
|
? undefined
|
||||||
: el.encryptedValue
|
: el.encryptedValue
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
|
||||||
@@ -293,7 +300,9 @@ 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,
|
||||||
secretValue: el.secret.encryptedValue
|
secretValue: !hasSecretReadAccess
|
||||||
|
? hiddenSecretValue
|
||||||
|
: el.secret.encryptedValue
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.secret.encryptedValue }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: el.secret.encryptedValue }).toString()
|
||||||
: "",
|
: "",
|
||||||
secretComment: el.secret.encryptedComment
|
secretComment: el.secret.encryptedComment
|
||||||
@@ -306,7 +315,9 @@ 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,
|
||||||
secretValue: el.secretVersion.encryptedValue
|
secretValue: !hasSecretReadAccess
|
||||||
|
? hiddenSecretValue
|
||||||
|
: el.secretVersion.encryptedValue
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedValue }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedValue }).toString()
|
||||||
: "",
|
: "",
|
||||||
secretComment: el.secretVersion.encryptedComment
|
secretComment: el.secretVersion.encryptedComment
|
||||||
|
@@ -101,10 +101,56 @@ export const azureClientSecretRotationFactory: TRotationFactory<
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a credential with the given keyId exists.
|
||||||
|
*/
|
||||||
|
const credentialExists = async (keyId: string): Promise<boolean> => {
|
||||||
|
const accessToken = await getAzureConnectionAccessToken(connection.id, appConnectionDAL, kmsService);
|
||||||
|
const endpoint = `${GRAPH_API_BASE}/applications/${objectId}/passwordCredentials`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await request.get<{ value: Array<{ keyId: string }> }>(endpoint, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.value?.some((credential) => credential.keyId === keyId) || false;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
let message;
|
||||||
|
if (
|
||||||
|
error.response?.data &&
|
||||||
|
typeof error.response.data === "object" &&
|
||||||
|
"error" in error.response.data &&
|
||||||
|
typeof (error.response.data as AzureErrorResponse).error.message === "string"
|
||||||
|
) {
|
||||||
|
message = (error.response.data as AzureErrorResponse).error.message;
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to check credential existence for app ${objectId}: ${
|
||||||
|
message || error.message || "Unknown error"
|
||||||
|
}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Unable to validate connection: verify credentials"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Revokes a client secret from the Azure app using its keyId.
|
* Revokes a client secret from the Azure app using its keyId.
|
||||||
|
* First checks if the credential exists before attempting revocation.
|
||||||
*/
|
*/
|
||||||
const revokeCredential = async (keyId: string) => {
|
const revokeCredential = async (keyId: string) => {
|
||||||
|
// Check if credential exists before attempting revocation
|
||||||
|
const exists = await credentialExists(keyId);
|
||||||
|
if (!exists) {
|
||||||
|
return; // Credential doesn't exist, nothing to revoke
|
||||||
|
}
|
||||||
|
|
||||||
const accessToken = await getAzureConnectionAccessToken(connection.id, appConnectionDAL, kmsService);
|
const accessToken = await getAzureConnectionAccessToken(connection.id, appConnectionDAL, kmsService);
|
||||||
const endpoint = `${GRAPH_API_BASE}/applications/${objectId}/removePassword`;
|
const endpoint = `${GRAPH_API_BASE}/applications/${objectId}/removePassword`;
|
||||||
|
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./oracledb-credentials-rotation-constants";
|
||||||
|
export * from "./oracledb-credentials-rotation-schemas";
|
||||||
|
export * from "./oracledb-credentials-rotation-types";
|
@@ -0,0 +1,20 @@
|
|||||||
|
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 ORACLEDB_CREDENTIALS_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = {
|
||||||
|
name: "OracleDB Credentials",
|
||||||
|
type: SecretRotation.OracleDBCredentials,
|
||||||
|
connection: AppConnection.OracleDB,
|
||||||
|
template: {
|
||||||
|
createUserStatement: `-- create user
|
||||||
|
CREATE USER INFISICAL_USER IDENTIFIED BY "temporary_password";
|
||||||
|
|
||||||
|
-- grant all privileges
|
||||||
|
GRANT ALL PRIVILEGES TO INFISICAL_USER;`,
|
||||||
|
secretsMapping: {
|
||||||
|
username: "ORACLEDB_USERNAME",
|
||||||
|
password: "ORACLEDB_PASSWORD"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@@ -0,0 +1,41 @@
|
|||||||
|
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 {
|
||||||
|
SqlCredentialsRotationParametersSchema,
|
||||||
|
SqlCredentialsRotationSecretsMappingSchema,
|
||||||
|
SqlCredentialsRotationTemplateSchema
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/shared/sql-credentials";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
export const OracleDBCredentialsRotationSchema = BaseSecretRotationSchema(SecretRotation.OracleDBCredentials).extend({
|
||||||
|
type: z.literal(SecretRotation.OracleDBCredentials),
|
||||||
|
parameters: SqlCredentialsRotationParametersSchema,
|
||||||
|
secretsMapping: SqlCredentialsRotationSecretsMappingSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateOracleDBCredentialsRotationSchema = BaseCreateSecretRotationSchema(
|
||||||
|
SecretRotation.OracleDBCredentials
|
||||||
|
).extend({
|
||||||
|
parameters: SqlCredentialsRotationParametersSchema,
|
||||||
|
secretsMapping: SqlCredentialsRotationSecretsMappingSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateOracleDBCredentialsRotationSchema = BaseUpdateSecretRotationSchema(
|
||||||
|
SecretRotation.OracleDBCredentials
|
||||||
|
).extend({
|
||||||
|
parameters: SqlCredentialsRotationParametersSchema.optional(),
|
||||||
|
secretsMapping: SqlCredentialsRotationSecretsMappingSchema.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const OracleDBCredentialsRotationListItemSchema = z.object({
|
||||||
|
name: z.literal("OracleDB Credentials"),
|
||||||
|
connection: z.literal(AppConnection.OracleDB),
|
||||||
|
type: z.literal(SecretRotation.OracleDBCredentials),
|
||||||
|
template: SqlCredentialsRotationTemplateSchema
|
||||||
|
});
|
@@ -0,0 +1,18 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TOracleDBConnection } from "../../app-connections/oracledb";
|
||||||
|
import {
|
||||||
|
CreateOracleDBCredentialsRotationSchema,
|
||||||
|
OracleDBCredentialsRotationListItemSchema,
|
||||||
|
OracleDBCredentialsRotationSchema
|
||||||
|
} from "./oracledb-credentials-rotation-schemas";
|
||||||
|
|
||||||
|
export type TOracleDBCredentialsRotation = z.infer<typeof OracleDBCredentialsRotationSchema>;
|
||||||
|
|
||||||
|
export type TOracleDBCredentialsRotationInput = z.infer<typeof CreateOracleDBCredentialsRotationSchema>;
|
||||||
|
|
||||||
|
export type TOracleDBCredentialsRotationListItem = z.infer<typeof OracleDBCredentialsRotationListItemSchema>;
|
||||||
|
|
||||||
|
export type TOracleDBCredentialsRotationWithConnection = TOracleDBCredentialsRotation & {
|
||||||
|
connection: TOracleDBConnection;
|
||||||
|
};
|
@@ -2,6 +2,7 @@ export enum SecretRotation {
|
|||||||
PostgresCredentials = "postgres-credentials",
|
PostgresCredentials = "postgres-credentials",
|
||||||
MsSqlCredentials = "mssql-credentials",
|
MsSqlCredentials = "mssql-credentials",
|
||||||
MySqlCredentials = "mysql-credentials",
|
MySqlCredentials = "mysql-credentials",
|
||||||
|
OracleDBCredentials = "oracledb-credentials",
|
||||||
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",
|
||||||
|
@@ -10,6 +10,7 @@ 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 { 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";
|
||||||
import { TSecretRotationV2ServiceFactoryDep } from "./secret-rotation-v2-service";
|
import { TSecretRotationV2ServiceFactoryDep } from "./secret-rotation-v2-service";
|
||||||
@@ -25,6 +26,7 @@ const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2List
|
|||||||
[SecretRotation.PostgresCredentials]: POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION,
|
[SecretRotation.PostgresCredentials]: POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.MsSqlCredentials]: MSSQL_CREDENTIALS_ROTATION_LIST_OPTION,
|
[SecretRotation.MsSqlCredentials]: MSSQL_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.MySqlCredentials]: MYSQL_CREDENTIALS_ROTATION_LIST_OPTION,
|
[SecretRotation.MySqlCredentials]: MYSQL_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||||
|
[SecretRotation.OracleDBCredentials]: ORACLEDB_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||||
[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,
|
||||||
|
@@ -5,6 +5,7 @@ export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
|
|||||||
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
|
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
|
||||||
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials",
|
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials",
|
||||||
[SecretRotation.MySqlCredentials]: "MySQL Credentials",
|
[SecretRotation.MySqlCredentials]: "MySQL Credentials",
|
||||||
|
[SecretRotation.OracleDBCredentials]: "OracleDB Credentials",
|
||||||
[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",
|
||||||
@@ -15,6 +16,7 @@ export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnectio
|
|||||||
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
|
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
|
||||||
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
|
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
|
||||||
[SecretRotation.MySqlCredentials]: AppConnection.MySql,
|
[SecretRotation.MySqlCredentials]: AppConnection.MySql,
|
||||||
|
[SecretRotation.OracleDBCredentials]: AppConnection.OracleDB,
|
||||||
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
|
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
|
||||||
[SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets,
|
[SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets,
|
||||||
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS,
|
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS,
|
||||||
|
@@ -123,6 +123,7 @@ const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplem
|
|||||||
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.MySqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.MySqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||||
|
[SecretRotation.OracleDBCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||||
[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,
|
||||||
|
@@ -45,6 +45,12 @@ import {
|
|||||||
TMySqlCredentialsRotationListItem,
|
TMySqlCredentialsRotationListItem,
|
||||||
TMySqlCredentialsRotationWithConnection
|
TMySqlCredentialsRotationWithConnection
|
||||||
} from "./mysql-credentials";
|
} from "./mysql-credentials";
|
||||||
|
import {
|
||||||
|
TOracleDBCredentialsRotation,
|
||||||
|
TOracleDBCredentialsRotationInput,
|
||||||
|
TOracleDBCredentialsRotationListItem,
|
||||||
|
TOracleDBCredentialsRotationWithConnection
|
||||||
|
} from "./oracledb-credentials";
|
||||||
import {
|
import {
|
||||||
TPostgresCredentialsRotation,
|
TPostgresCredentialsRotation,
|
||||||
TPostgresCredentialsRotationInput,
|
TPostgresCredentialsRotationInput,
|
||||||
@@ -58,6 +64,7 @@ export type TSecretRotationV2 =
|
|||||||
| TPostgresCredentialsRotation
|
| TPostgresCredentialsRotation
|
||||||
| TMsSqlCredentialsRotation
|
| TMsSqlCredentialsRotation
|
||||||
| TMySqlCredentialsRotation
|
| TMySqlCredentialsRotation
|
||||||
|
| TOracleDBCredentialsRotation
|
||||||
| TAuth0ClientSecretRotation
|
| TAuth0ClientSecretRotation
|
||||||
| TAzureClientSecretRotation
|
| TAzureClientSecretRotation
|
||||||
| TLdapPasswordRotation
|
| TLdapPasswordRotation
|
||||||
@@ -67,6 +74,7 @@ export type TSecretRotationV2WithConnection =
|
|||||||
| TPostgresCredentialsRotationWithConnection
|
| TPostgresCredentialsRotationWithConnection
|
||||||
| TMsSqlCredentialsRotationWithConnection
|
| TMsSqlCredentialsRotationWithConnection
|
||||||
| TMySqlCredentialsRotationWithConnection
|
| TMySqlCredentialsRotationWithConnection
|
||||||
|
| TOracleDBCredentialsRotationWithConnection
|
||||||
| TAuth0ClientSecretRotationWithConnection
|
| TAuth0ClientSecretRotationWithConnection
|
||||||
| TAzureClientSecretRotationWithConnection
|
| TAzureClientSecretRotationWithConnection
|
||||||
| TLdapPasswordRotationWithConnection
|
| TLdapPasswordRotationWithConnection
|
||||||
@@ -83,6 +91,7 @@ export type TSecretRotationV2Input =
|
|||||||
| TPostgresCredentialsRotationInput
|
| TPostgresCredentialsRotationInput
|
||||||
| TMsSqlCredentialsRotationInput
|
| TMsSqlCredentialsRotationInput
|
||||||
| TMySqlCredentialsRotationInput
|
| TMySqlCredentialsRotationInput
|
||||||
|
| TOracleDBCredentialsRotationInput
|
||||||
| TAuth0ClientSecretRotationInput
|
| TAuth0ClientSecretRotationInput
|
||||||
| TAzureClientSecretRotationInput
|
| TAzureClientSecretRotationInput
|
||||||
| TLdapPasswordRotationInput
|
| TLdapPasswordRotationInput
|
||||||
@@ -92,6 +101,7 @@ export type TSecretRotationV2ListItem =
|
|||||||
| TPostgresCredentialsRotationListItem
|
| TPostgresCredentialsRotationListItem
|
||||||
| TMsSqlCredentialsRotationListItem
|
| TMsSqlCredentialsRotationListItem
|
||||||
| TMySqlCredentialsRotationListItem
|
| TMySqlCredentialsRotationListItem
|
||||||
|
| TOracleDBCredentialsRotationListItem
|
||||||
| TAuth0ClientSecretRotationListItem
|
| TAuth0ClientSecretRotationListItem
|
||||||
| TAzureClientSecretRotationListItem
|
| TAzureClientSecretRotationListItem
|
||||||
| TLdapPasswordRotationListItem
|
| TLdapPasswordRotationListItem
|
||||||
|
@@ -1,18 +1,19 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { Auth0ClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
import { Auth0ClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
||||||
|
import { AwsIamUserSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/aws-iam-user-secret";
|
||||||
import { AzureClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret";
|
import { AzureClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret";
|
||||||
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 { 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";
|
||||||
|
|
||||||
import { AwsIamUserSecretRotationSchema } from "./aws-iam-user-secret";
|
|
||||||
|
|
||||||
export const SecretRotationV2Schema = z.discriminatedUnion("type", [
|
export const SecretRotationV2Schema = z.discriminatedUnion("type", [
|
||||||
PostgresCredentialsRotationSchema,
|
PostgresCredentialsRotationSchema,
|
||||||
MsSqlCredentialsRotationSchema,
|
MsSqlCredentialsRotationSchema,
|
||||||
MySqlCredentialsRotationSchema,
|
MySqlCredentialsRotationSchema,
|
||||||
|
OracleDBCredentialsRotationSchema,
|
||||||
Auth0ClientSecretRotationSchema,
|
Auth0ClientSecretRotationSchema,
|
||||||
AzureClientSecretRotationSchema,
|
AzureClientSecretRotationSchema,
|
||||||
LdapPasswordRotationSchema,
|
LdapPasswordRotationSchema,
|
||||||
|
@@ -2,14 +2,15 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { TMsSqlCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
import { TMsSqlCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||||
import { TMySqlCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
|
import { TMySqlCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
|
||||||
|
import { TOracleDBCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/oracledb-credentials";
|
||||||
import { TPostgresCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
import { TPostgresCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||||
|
import { SqlCredentialsRotationGeneratedCredentialsSchema } from "@app/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-schemas";
|
||||||
import { SqlCredentialsRotationGeneratedCredentialsSchema } from "./sql-credentials-rotation-schemas";
|
|
||||||
|
|
||||||
export type TSqlCredentialsRotationWithConnection =
|
export type TSqlCredentialsRotationWithConnection =
|
||||||
| TPostgresCredentialsRotationWithConnection
|
| TPostgresCredentialsRotationWithConnection
|
||||||
| TMsSqlCredentialsRotationWithConnection
|
| TMsSqlCredentialsRotationWithConnection
|
||||||
| TMySqlCredentialsRotationWithConnection;
|
| TMySqlCredentialsRotationWithConnection
|
||||||
|
| TOracleDBCredentialsRotationWithConnection;
|
||||||
|
|
||||||
export type TSqlCredentialsRotationGeneratedCredentials = z.infer<
|
export type TSqlCredentialsRotationGeneratedCredentials = z.infer<
|
||||||
typeof SqlCredentialsRotationGeneratedCredentialsSchema
|
typeof SqlCredentialsRotationGeneratedCredentialsSchema
|
||||||
|
@@ -178,6 +178,13 @@ export const getDbSetQuery = (db: TDbProviderClients, variables: { username: str
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (db === TDbProviderClients.OracleDB) {
|
||||||
|
return {
|
||||||
|
query: `ALTER USER ?? IDENTIFIED BY "${variables.password}"`,
|
||||||
|
variables: [variables.username]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// add more based on client
|
// add more based on client
|
||||||
return {
|
return {
|
||||||
query: `ALTER USER ?? IDENTIFIED BY '${variables.password}'`,
|
query: `ALTER USER ?? IDENTIFIED BY '${variables.password}'`,
|
||||||
|
@@ -10,7 +10,8 @@ export enum TDbProviderClients {
|
|||||||
// mysql and maria db
|
// mysql and maria db
|
||||||
MySql = "mysql",
|
MySql = "mysql",
|
||||||
|
|
||||||
MsSqlServer = "mssql"
|
MsSqlServer = "mssql",
|
||||||
|
OracleDB = "oracledb"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TAwsProviderSystems {
|
export enum TAwsProviderSystems {
|
||||||
|
@@ -21,6 +21,7 @@ export enum ApiDocsTags {
|
|||||||
TokenAuth = "Token Auth",
|
TokenAuth = "Token Auth",
|
||||||
UniversalAuth = "Universal Auth",
|
UniversalAuth = "Universal Auth",
|
||||||
GcpAuth = "GCP Auth",
|
GcpAuth = "GCP Auth",
|
||||||
|
AliCloudAuth = "Alibaba Cloud Auth",
|
||||||
AwsAuth = "AWS Auth",
|
AwsAuth = "AWS Auth",
|
||||||
OciAuth = "OCI Auth",
|
OciAuth = "OCI Auth",
|
||||||
AzureAuth = "Azure Auth",
|
AzureAuth = "Azure Auth",
|
||||||
@@ -243,6 +244,43 @@ export const LDAP_AUTH = {
|
|||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const ALICLOUD_AUTH = {
|
||||||
|
LOGIN: {
|
||||||
|
identityId: "The ID of the identity to login.",
|
||||||
|
Action: "The Alibaba Cloud API action. For STS GetCallerIdentity, this should be 'GetCallerIdentity'.",
|
||||||
|
Format: "The response format. For STS GetCallerIdentity, this should be 'JSON'.",
|
||||||
|
Version: "The API version. This should be in 'YYYY-MM-DD' format (e.g., '2015-04-01').",
|
||||||
|
AccessKeyId: "The AccessKey ID of the RAM user or STS token.",
|
||||||
|
SignatureMethod: "The signature algorithm. For STS GetCallerIdentity, this should be 'HMAC-SHA1'.",
|
||||||
|
Timestamp: "The timestamp of the request in UTC, formatted as 'YYYY-MM-DDTHH:mm:ssZ'.",
|
||||||
|
SignatureVersion: "The signature version. For STS GetCallerIdentity, this should be '1.0'.",
|
||||||
|
SignatureNonce: "A unique random string to prevent replay attacks.",
|
||||||
|
Signature: "The signature string calculated based on the request parameters and AccessKey Secret."
|
||||||
|
},
|
||||||
|
ATTACH: {
|
||||||
|
identityId: "The ID of the identity to attach the configuration onto.",
|
||||||
|
allowedArns: "The comma-separated list of trusted ARNs that are allowed to authenticate with Infisical.",
|
||||||
|
accessTokenTTL: "The lifetime for an access token in seconds.",
|
||||||
|
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
|
||||||
|
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used.",
|
||||||
|
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from."
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
identityId: "The ID of the identity to update the auth method for.",
|
||||||
|
allowedArns: "The comma-separated list of trusted ARNs that are allowed to authenticate with Infisical.",
|
||||||
|
accessTokenTTL: "The new lifetime for an access token in seconds.",
|
||||||
|
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
|
||||||
|
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.",
|
||||||
|
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from."
|
||||||
|
},
|
||||||
|
RETRIEVE: {
|
||||||
|
identityId: "The ID of the identity to retrieve the auth method for."
|
||||||
|
},
|
||||||
|
REVOKE: {
|
||||||
|
identityId: "The ID of the identity to revoke the auth method for."
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const AWS_AUTH = {
|
export const AWS_AUTH = {
|
||||||
LOGIN: {
|
LOGIN: {
|
||||||
identityId: "The ID of the identity to login.",
|
identityId: "The ID of the identity to login.",
|
||||||
@@ -1113,6 +1151,14 @@ export const DYNAMIC_SECRET_LEASES = {
|
|||||||
leaseId: "The ID of the dynamic secret lease.",
|
leaseId: "The ID of the dynamic secret lease.",
|
||||||
isForced:
|
isForced:
|
||||||
"A boolean flag to delete the the dynamic secret from Infisical without trying to remove it from external provider. Used when the dynamic secret got modified externally."
|
"A boolean flag to delete the the dynamic secret from Infisical without trying to remove it from external provider. Used when the dynamic secret got modified externally."
|
||||||
|
},
|
||||||
|
KUBERNETES: {
|
||||||
|
CREATE: {
|
||||||
|
config: {
|
||||||
|
namespace:
|
||||||
|
"The Kubernetes namespace to create the lease in. If not specified, the first namespace defined in the configuration will be used."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
export const SECRET_TAGS = {
|
export const SECRET_TAGS = {
|
||||||
@@ -2162,6 +2208,11 @@ export const AppConnections = {
|
|||||||
code: "The OAuth code to use to connect with Azure Client Secrets.",
|
code: "The OAuth code to use to connect with Azure Client Secrets.",
|
||||||
tenantId: "The Tenant ID to use to connect with Azure Client Secrets."
|
tenantId: "The Tenant ID to use to connect with Azure Client Secrets."
|
||||||
},
|
},
|
||||||
|
AZURE_DEVOPS: {
|
||||||
|
code: "The OAuth code to use to connect with Azure DevOps.",
|
||||||
|
tenantId: "The Tenant ID to use to connect with Azure DevOps.",
|
||||||
|
orgName: "The Organization name to use to connect with Azure DevOps."
|
||||||
|
},
|
||||||
OCI: {
|
OCI: {
|
||||||
userOcid: "The OCID (Oracle Cloud Identifier) of the user making the request.",
|
userOcid: "The OCID (Oracle Cloud Identifier) of the user making the request.",
|
||||||
tenancyOcid: "The OCID (Oracle Cloud Identifier) of the tenancy in Oracle Cloud Infrastructure.",
|
tenancyOcid: "The OCID (Oracle Cloud Identifier) of the tenancy in Oracle Cloud Infrastructure.",
|
||||||
@@ -2276,6 +2327,10 @@ export const SecretSyncs = {
|
|||||||
"The URL of the Azure App Configuration to sync secrets to. Example: https://example.azconfig.io/",
|
"The URL of the Azure App Configuration to sync secrets to. Example: https://example.azconfig.io/",
|
||||||
label: "An optional label to assign to secrets created in Azure App Configuration."
|
label: "An optional label to assign to secrets created in Azure App Configuration."
|
||||||
},
|
},
|
||||||
|
AZURE_DEVOPS: {
|
||||||
|
devopsProjectId: "The ID of the Azure DevOps project to sync secrets to.",
|
||||||
|
devopsProjectName: "The name of the Azure DevOps project to sync secrets to."
|
||||||
|
},
|
||||||
GCP: {
|
GCP: {
|
||||||
scope: "The Google project scope that secrets should be synced to.",
|
scope: "The Google project scope that secrets should be synced to.",
|
||||||
projectId: "The ID of the Google project secrets should be synced to.",
|
projectId: "The ID of the Google project secrets should be synced to.",
|
||||||
|
@@ -149,8 +149,8 @@ const setupProxyServer = async ({
|
|||||||
protocol = GatewayProxyProtocol.Tcp,
|
protocol = GatewayProxyProtocol.Tcp,
|
||||||
httpsAgent
|
httpsAgent
|
||||||
}: {
|
}: {
|
||||||
targetHost: string;
|
targetHost?: string;
|
||||||
targetPort: number;
|
targetPort?: number;
|
||||||
relayPort: number;
|
relayPort: number;
|
||||||
relayHost: string;
|
relayHost: string;
|
||||||
tlsOptions: TGatewayTlsOptions;
|
tlsOptions: TGatewayTlsOptions;
|
||||||
@@ -183,9 +183,19 @@ const setupProxyServer = async ({
|
|||||||
let command: string;
|
let command: string;
|
||||||
|
|
||||||
if (protocol === GatewayProxyProtocol.Http) {
|
if (protocol === GatewayProxyProtocol.Http) {
|
||||||
|
if (!targetHost && !targetPort) {
|
||||||
|
command = `FORWARD-HTTP`;
|
||||||
|
logger.debug(`Using HTTP proxy mode, no target URL provided [command=${command.trim()}]`);
|
||||||
|
} else {
|
||||||
|
if (!targetHost || targetPort === undefined) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Target host and port are required for HTTP proxy mode with custom target`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const targetUrl = `${targetHost}:${targetPort}`; // note(daniel): targetHost MUST include the scheme (https|http)
|
const targetUrl = `${targetHost}:${targetPort}`; // note(daniel): targetHost MUST include the scheme (https|http)
|
||||||
command = `FORWARD-HTTP ${targetUrl}`;
|
command = `FORWARD-HTTP ${targetUrl}`;
|
||||||
logger.debug(`Using HTTP proxy mode: ${command.trim()}`);
|
logger.debug(`Using HTTP proxy mode, custom target URL provided [command=${command.trim()}]`);
|
||||||
|
|
||||||
// extract ca certificate from httpsAgent if present
|
// extract ca certificate from httpsAgent if present
|
||||||
if (httpsAgent && targetHost.startsWith("https://")) {
|
if (httpsAgent && targetHost.startsWith("https://")) {
|
||||||
@@ -198,12 +208,19 @@ const setupProxyServer = async ({
|
|||||||
const rejectUnauthorized = agentOptions.rejectUnauthorized !== false;
|
const rejectUnauthorized = agentOptions.rejectUnauthorized !== false;
|
||||||
command += ` verify=${rejectUnauthorized}`;
|
command += ` verify=${rejectUnauthorized}`;
|
||||||
|
|
||||||
logger.debug(`Using HTTP proxy mode [command=${command.trim()}]`);
|
logger.debug(`Using HTTP proxy mode, custom target URL provided [command=${command.trim()}]`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
command += "\n";
|
command += "\n";
|
||||||
} else if (protocol === GatewayProxyProtocol.Tcp) {
|
} else if (protocol === GatewayProxyProtocol.Tcp) {
|
||||||
|
if (!targetHost || !targetPort) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Target host and port are required for TCP proxy mode`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// For TCP mode, send FORWARD-TCP with host:port
|
// For TCP mode, send FORWARD-TCP with host:port
|
||||||
command = `FORWARD-TCP ${targetHost}:${targetPort}\n`;
|
command = `FORWARD-TCP ${targetHost}:${targetPort}\n`;
|
||||||
logger.debug(`Using TCP proxy mode: ${command.trim()}`);
|
logger.debug(`Using TCP proxy mode: ${command.trim()}`);
|
||||||
|
@@ -10,12 +10,13 @@ export enum GatewayProxyProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum GatewayHttpProxyActions {
|
export enum GatewayHttpProxyActions {
|
||||||
InjectGatewayK8sServiceAccountToken = "inject-k8s-sa-auth-token"
|
InjectGatewayK8sServiceAccountToken = "inject-k8s-sa-auth-token",
|
||||||
|
UseGatewayK8sServiceAccount = "use-k8s-sa"
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGatewayProxyOptions {
|
export interface IGatewayProxyOptions {
|
||||||
targetHost: string;
|
targetHost?: string;
|
||||||
targetPort: number;
|
targetPort?: number;
|
||||||
relayHost: string;
|
relayHost: string;
|
||||||
relayPort: number;
|
relayPort: number;
|
||||||
tlsOptions: TGatewayTlsOptions;
|
tlsOptions: TGatewayTlsOptions;
|
||||||
|
@@ -172,6 +172,8 @@ import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal";
|
|||||||
import { identityServiceFactory } from "@app/services/identity/identity-service";
|
import { identityServiceFactory } from "@app/services/identity/identity-service";
|
||||||
import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal";
|
import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal";
|
||||||
import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
||||||
|
import { identityAliCloudAuthDALFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-dal";
|
||||||
|
import { identityAliCloudAuthServiceFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-service";
|
||||||
import { identityAwsAuthDALFactory } from "@app/services/identity-aws-auth/identity-aws-auth-dal";
|
import { identityAwsAuthDALFactory } from "@app/services/identity-aws-auth/identity-aws-auth-dal";
|
||||||
import { identityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
|
import { identityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
|
||||||
import { identityAzureAuthDALFactory } from "@app/services/identity-azure-auth/identity-azure-auth-dal";
|
import { identityAzureAuthDALFactory } from "@app/services/identity-azure-auth/identity-azure-auth-dal";
|
||||||
@@ -383,6 +385,7 @@ export const registerRoutes = async (
|
|||||||
const identityUaDAL = identityUaDALFactory(db);
|
const identityUaDAL = identityUaDALFactory(db);
|
||||||
const identityKubernetesAuthDAL = identityKubernetesAuthDALFactory(db);
|
const identityKubernetesAuthDAL = identityKubernetesAuthDALFactory(db);
|
||||||
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
|
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
|
||||||
|
const identityAliCloudAuthDAL = identityAliCloudAuthDALFactory(db);
|
||||||
const identityAwsAuthDAL = identityAwsAuthDALFactory(db);
|
const identityAwsAuthDAL = identityAwsAuthDALFactory(db);
|
||||||
const identityGcpAuthDAL = identityGcpAuthDALFactory(db);
|
const identityGcpAuthDAL = identityGcpAuthDALFactory(db);
|
||||||
const identityOciAuthDAL = identityOciAuthDALFactory(db);
|
const identityOciAuthDAL = identityOciAuthDALFactory(db);
|
||||||
@@ -1482,6 +1485,14 @@ export const registerRoutes = async (
|
|||||||
licenseService
|
licenseService
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const identityAliCloudAuthService = identityAliCloudAuthServiceFactory({
|
||||||
|
identityAccessTokenDAL,
|
||||||
|
identityAliCloudAuthDAL,
|
||||||
|
identityOrgMembershipDAL,
|
||||||
|
licenseService,
|
||||||
|
permissionService
|
||||||
|
});
|
||||||
|
|
||||||
const identityAwsAuthService = identityAwsAuthServiceFactory({
|
const identityAwsAuthService = identityAwsAuthServiceFactory({
|
||||||
identityAccessTokenDAL,
|
identityAccessTokenDAL,
|
||||||
identityAwsAuthDAL,
|
identityAwsAuthDAL,
|
||||||
@@ -1931,6 +1942,7 @@ export const registerRoutes = async (
|
|||||||
identityUa: identityUaService,
|
identityUa: identityUaService,
|
||||||
identityKubernetesAuth: identityKubernetesAuthService,
|
identityKubernetesAuth: identityKubernetesAuthService,
|
||||||
identityGcpAuth: identityGcpAuthService,
|
identityGcpAuth: identityGcpAuthService,
|
||||||
|
identityAliCloudAuth: identityAliCloudAuthService,
|
||||||
identityAwsAuth: identityAwsAuthService,
|
identityAwsAuth: identityAwsAuthService,
|
||||||
identityAzureAuth: identityAzureAuthService,
|
identityAzureAuth: identityAzureAuthService,
|
||||||
identityOciAuth: identityOciAuthService,
|
identityOciAuth: identityOciAuthService,
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/ee/services/app-connections/oci";
|
import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/ee/services/app-connections/oci";
|
||||||
|
import {
|
||||||
|
OracleDBConnectionListItemSchema,
|
||||||
|
SanitizedOracleDBConnectionSchema
|
||||||
|
} from "@app/ee/services/app-connections/oracledb";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ApiDocsTags } from "@app/lib/api-docs";
|
import { ApiDocsTags } from "@app/lib/api-docs";
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
@@ -19,6 +23,10 @@ import {
|
|||||||
AzureClientSecretsConnectionListItemSchema,
|
AzureClientSecretsConnectionListItemSchema,
|
||||||
SanitizedAzureClientSecretsConnectionSchema
|
SanitizedAzureClientSecretsConnectionSchema
|
||||||
} from "@app/services/app-connection/azure-client-secrets";
|
} from "@app/services/app-connection/azure-client-secrets";
|
||||||
|
import {
|
||||||
|
AzureDevOpsConnectionListItemSchema,
|
||||||
|
SanitizedAzureDevOpsConnectionSchema
|
||||||
|
} from "@app/services/app-connection/azure-devops/azure-devops-schemas";
|
||||||
import {
|
import {
|
||||||
AzureKeyVaultConnectionListItemSchema,
|
AzureKeyVaultConnectionListItemSchema,
|
||||||
SanitizedAzureKeyVaultConnectionSchema
|
SanitizedAzureKeyVaultConnectionSchema
|
||||||
@@ -75,6 +83,7 @@ const SanitizedAppConnectionSchema = z.union([
|
|||||||
...SanitizedGcpConnectionSchema.options,
|
...SanitizedGcpConnectionSchema.options,
|
||||||
...SanitizedAzureKeyVaultConnectionSchema.options,
|
...SanitizedAzureKeyVaultConnectionSchema.options,
|
||||||
...SanitizedAzureAppConfigurationConnectionSchema.options,
|
...SanitizedAzureAppConfigurationConnectionSchema.options,
|
||||||
|
...SanitizedAzureDevOpsConnectionSchema.options,
|
||||||
...SanitizedDatabricksConnectionSchema.options,
|
...SanitizedDatabricksConnectionSchema.options,
|
||||||
...SanitizedHumanitecConnectionSchema.options,
|
...SanitizedHumanitecConnectionSchema.options,
|
||||||
...SanitizedTerraformCloudConnectionSchema.options,
|
...SanitizedTerraformCloudConnectionSchema.options,
|
||||||
@@ -90,6 +99,7 @@ const SanitizedAppConnectionSchema = z.union([
|
|||||||
...SanitizedLdapConnectionSchema.options,
|
...SanitizedLdapConnectionSchema.options,
|
||||||
...SanitizedTeamCityConnectionSchema.options,
|
...SanitizedTeamCityConnectionSchema.options,
|
||||||
...SanitizedOCIConnectionSchema.options,
|
...SanitizedOCIConnectionSchema.options,
|
||||||
|
...SanitizedOracleDBConnectionSchema.options,
|
||||||
...SanitizedOnePassConnectionSchema.options
|
...SanitizedOnePassConnectionSchema.options
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -100,6 +110,7 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
|||||||
GcpConnectionListItemSchema,
|
GcpConnectionListItemSchema,
|
||||||
AzureKeyVaultConnectionListItemSchema,
|
AzureKeyVaultConnectionListItemSchema,
|
||||||
AzureAppConfigurationConnectionListItemSchema,
|
AzureAppConfigurationConnectionListItemSchema,
|
||||||
|
AzureDevOpsConnectionListItemSchema,
|
||||||
DatabricksConnectionListItemSchema,
|
DatabricksConnectionListItemSchema,
|
||||||
HumanitecConnectionListItemSchema,
|
HumanitecConnectionListItemSchema,
|
||||||
TerraformCloudConnectionListItemSchema,
|
TerraformCloudConnectionListItemSchema,
|
||||||
@@ -115,6 +126,7 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
|||||||
LdapConnectionListItemSchema,
|
LdapConnectionListItemSchema,
|
||||||
TeamCityConnectionListItemSchema,
|
TeamCityConnectionListItemSchema,
|
||||||
OCIConnectionListItemSchema,
|
OCIConnectionListItemSchema,
|
||||||
|
OracleDBConnectionListItemSchema,
|
||||||
OnePassConnectionListItemSchema
|
OnePassConnectionListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@@ -0,0 +1,49 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateAzureDevOpsConnectionSchema,
|
||||||
|
SanitizedAzureDevOpsConnectionSchema,
|
||||||
|
UpdateAzureDevOpsConnectionSchema
|
||||||
|
} from "@app/services/app-connection/azure-devops/azure-devops-schemas";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerAzureDevOpsConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.AzureDevOps,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedAzureDevOpsConnectionSchema,
|
||||||
|
createSchema: CreateAzureDevOpsConnectionSchema,
|
||||||
|
updateSchema: UpdateAzureDevOpsConnectionSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/projects`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
projects: z.object({ name: z.string(), id: z.string(), appId: z.string() }).array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
|
||||||
|
const projects = await server.services.appConnection.azureDevOps.listProjects(connectionId, req.permission);
|
||||||
|
|
||||||
|
return { projects };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -1,4 +1,5 @@
|
|||||||
import { registerOCIConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/oci-connection-router";
|
import { registerOCIConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/oci-connection-router";
|
||||||
|
import { registerOracleDBConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/oracledb-connection-router";
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
import { registerOnePassConnectionRouter } from "./1password-connection-router";
|
import { registerOnePassConnectionRouter } from "./1password-connection-router";
|
||||||
@@ -6,6 +7,7 @@ import { registerAuth0ConnectionRouter } from "./auth0-connection-router";
|
|||||||
import { registerAwsConnectionRouter } from "./aws-connection-router";
|
import { registerAwsConnectionRouter } from "./aws-connection-router";
|
||||||
import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-configuration-connection-router";
|
import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-configuration-connection-router";
|
||||||
import { registerAzureClientSecretsConnectionRouter } from "./azure-client-secrets-connection-router";
|
import { registerAzureClientSecretsConnectionRouter } from "./azure-client-secrets-connection-router";
|
||||||
|
import { registerAzureDevOpsConnectionRouter } from "./azure-devops-connection-router";
|
||||||
import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router";
|
import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router";
|
||||||
import { registerCamundaConnectionRouter } from "./camunda-connection-router";
|
import { registerCamundaConnectionRouter } from "./camunda-connection-router";
|
||||||
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
|
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
|
||||||
@@ -34,6 +36,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
|||||||
[AppConnection.AzureKeyVault]: registerAzureKeyVaultConnectionRouter,
|
[AppConnection.AzureKeyVault]: registerAzureKeyVaultConnectionRouter,
|
||||||
[AppConnection.AzureAppConfiguration]: registerAzureAppConfigurationConnectionRouter,
|
[AppConnection.AzureAppConfiguration]: registerAzureAppConfigurationConnectionRouter,
|
||||||
[AppConnection.AzureClientSecrets]: registerAzureClientSecretsConnectionRouter,
|
[AppConnection.AzureClientSecrets]: registerAzureClientSecretsConnectionRouter,
|
||||||
|
[AppConnection.AzureDevOps]: registerAzureDevOpsConnectionRouter,
|
||||||
[AppConnection.Databricks]: registerDatabricksConnectionRouter,
|
[AppConnection.Databricks]: registerDatabricksConnectionRouter,
|
||||||
[AppConnection.Humanitec]: registerHumanitecConnectionRouter,
|
[AppConnection.Humanitec]: registerHumanitecConnectionRouter,
|
||||||
[AppConnection.TerraformCloud]: registerTerraformCloudConnectionRouter,
|
[AppConnection.TerraformCloud]: registerTerraformCloudConnectionRouter,
|
||||||
@@ -48,5 +51,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
|||||||
[AppConnection.LDAP]: registerLdapConnectionRouter,
|
[AppConnection.LDAP]: registerLdapConnectionRouter,
|
||||||
[AppConnection.TeamCity]: registerTeamCityConnectionRouter,
|
[AppConnection.TeamCity]: registerTeamCityConnectionRouter,
|
||||||
[AppConnection.OCI]: registerOCIConnectionRouter,
|
[AppConnection.OCI]: registerOCIConnectionRouter,
|
||||||
|
[AppConnection.OracleDB]: registerOracleDBConnectionRouter,
|
||||||
[AppConnection.OnePass]: registerOnePassConnectionRouter
|
[AppConnection.OnePass]: registerOnePassConnectionRouter
|
||||||
};
|
};
|
||||||
|
381
backend/src/server/routes/v1/identity-alicloud-auth-router.ts
Normal file
381
backend/src/server/routes/v1/identity-alicloud-auth-router.ts
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
import RE2 from "re2";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { IdentityAlicloudAuthsSchema } from "@app/db/schemas";
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { ALICLOUD_AUTH, ApiDocsTags } from "@app/lib/api-docs";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||||
|
import { validateArns } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-validators";
|
||||||
|
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
|
||||||
|
|
||||||
|
export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/alicloud-auth/login",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.AliCloudAuth],
|
||||||
|
description: "Login with Alibaba Cloud Auth",
|
||||||
|
body: z.object({
|
||||||
|
identityId: z.string().trim().describe(ALICLOUD_AUTH.LOGIN.identityId),
|
||||||
|
Action: z.enum(["GetCallerIdentity"]).describe(ALICLOUD_AUTH.LOGIN.Action),
|
||||||
|
Format: z.enum(["JSON"]).describe(ALICLOUD_AUTH.LOGIN.Format),
|
||||||
|
Version: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => new RE2("^\\d{4}-\\d{2}-\\d{2}$").test(val), {
|
||||||
|
message: "Version must be in YYYY-MM-DD format"
|
||||||
|
})
|
||||||
|
.describe(ALICLOUD_AUTH.LOGIN.Version),
|
||||||
|
AccessKeyId: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => new RE2("^[A-Za-z0-9]+$").test(val), {
|
||||||
|
message: "AccessKeyId must be alphanumeric"
|
||||||
|
})
|
||||||
|
.describe(ALICLOUD_AUTH.LOGIN.AccessKeyId),
|
||||||
|
SignatureMethod: z.enum(["HMAC-SHA1"]).describe(ALICLOUD_AUTH.LOGIN.SignatureMethod),
|
||||||
|
Timestamp: z
|
||||||
|
.string()
|
||||||
|
.datetime({
|
||||||
|
message: "Timestamp must be in YYYY-MM-DDTHH:mm:ssZ format"
|
||||||
|
})
|
||||||
|
.refine((val) => val.endsWith("Z"), {
|
||||||
|
message: "Timestamp must be in YYYY-MM-DDTHH:mm:ssZ format"
|
||||||
|
})
|
||||||
|
.describe(ALICLOUD_AUTH.LOGIN.Timestamp),
|
||||||
|
SignatureVersion: z.enum(["1.0"]).describe(ALICLOUD_AUTH.LOGIN.SignatureVersion),
|
||||||
|
SignatureNonce: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => new RE2("^[a-zA-Z0-9-_.]+$").test(val), {
|
||||||
|
message:
|
||||||
|
"SignatureNonce must be at least 1 character long and contain only URL-safe characters (alphanumeric, -, _, .)"
|
||||||
|
})
|
||||||
|
.describe(ALICLOUD_AUTH.LOGIN.SignatureNonce),
|
||||||
|
Signature: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => new RE2("^[A-Za-z0-9+/=]+$").test(val), {
|
||||||
|
message: "Signature must be base64 characters"
|
||||||
|
})
|
||||||
|
.describe(ALICLOUD_AUTH.LOGIN.Signature)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
accessToken: z.string(),
|
||||||
|
expiresIn: z.coerce.number(),
|
||||||
|
accessTokenMaxTTL: z.coerce.number(),
|
||||||
|
tokenType: z.literal("Bearer")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const { identityAliCloudAuth, accessToken, identityAccessToken, identityMembershipOrg } =
|
||||||
|
await server.services.identityAliCloudAuth.login(req.body);
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: identityMembershipOrg?.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.LOGIN_IDENTITY_ALICLOUD_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: identityAliCloudAuth.identityId,
|
||||||
|
identityAccessTokenId: identityAccessToken.id,
|
||||||
|
identityAliCloudAuthId: identityAliCloudAuth.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken,
|
||||||
|
tokenType: "Bearer" as const,
|
||||||
|
expiresIn: identityAliCloudAuth.accessTokenTTL,
|
||||||
|
accessTokenMaxTTL: identityAliCloudAuth.accessTokenMaxTTL
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/alicloud-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.AliCloudAuth],
|
||||||
|
description: "Attach Alibaba Cloud Auth configuration onto identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().trim().describe(ALICLOUD_AUTH.ATTACH.identityId)
|
||||||
|
}),
|
||||||
|
body: z
|
||||||
|
.object({
|
||||||
|
allowedArns: validateArns.describe(ALICLOUD_AUTH.ATTACH.allowedArns),
|
||||||
|
accessTokenTrustedIps: z
|
||||||
|
.object({
|
||||||
|
ipAddress: z.string().trim()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||||
|
.describe(ALICLOUD_AUTH.ATTACH.accessTokenTrustedIps),
|
||||||
|
accessTokenTTL: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(0)
|
||||||
|
.max(315360000)
|
||||||
|
.default(2592000)
|
||||||
|
.describe(ALICLOUD_AUTH.ATTACH.accessTokenTTL),
|
||||||
|
accessTokenMaxTTL: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(1)
|
||||||
|
.max(315360000)
|
||||||
|
.default(2592000)
|
||||||
|
.describe(ALICLOUD_AUTH.ATTACH.accessTokenMaxTTL),
|
||||||
|
accessTokenNumUsesLimit: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(0)
|
||||||
|
.default(0)
|
||||||
|
.describe(ALICLOUD_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
|
||||||
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
|
),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identityAliCloudAuth: IdentityAlicloudAuthsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identityAliCloudAuth = await server.services.identityAliCloudAuth.attachAliCloudAuth({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body,
|
||||||
|
identityId: req.params.identityId,
|
||||||
|
isActorSuperAdmin: isSuperAdmin(req.auth)
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: identityAliCloudAuth.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.ADD_IDENTITY_ALICLOUD_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: identityAliCloudAuth.identityId,
|
||||||
|
allowedArns: identityAliCloudAuth.allowedArns,
|
||||||
|
accessTokenTTL: identityAliCloudAuth.accessTokenTTL,
|
||||||
|
accessTokenMaxTTL: identityAliCloudAuth.accessTokenMaxTTL,
|
||||||
|
accessTokenTrustedIps: identityAliCloudAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
|
||||||
|
accessTokenNumUsesLimit: identityAliCloudAuth.accessTokenNumUsesLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identityAliCloudAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/alicloud-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.AliCloudAuth],
|
||||||
|
description: "Update Alibaba Cloud Auth configuration on identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().describe(ALICLOUD_AUTH.UPDATE.identityId)
|
||||||
|
}),
|
||||||
|
body: z
|
||||||
|
.object({
|
||||||
|
allowedArns: validateArns.describe(ALICLOUD_AUTH.UPDATE.allowedArns),
|
||||||
|
accessTokenTrustedIps: z
|
||||||
|
.object({
|
||||||
|
ipAddress: z.string().trim()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.optional()
|
||||||
|
.describe(ALICLOUD_AUTH.UPDATE.accessTokenTrustedIps),
|
||||||
|
accessTokenTTL: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(0)
|
||||||
|
.max(315360000)
|
||||||
|
.optional()
|
||||||
|
.describe(ALICLOUD_AUTH.UPDATE.accessTokenTTL),
|
||||||
|
accessTokenNumUsesLimit: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(0)
|
||||||
|
.optional()
|
||||||
|
.describe(ALICLOUD_AUTH.UPDATE.accessTokenNumUsesLimit),
|
||||||
|
accessTokenMaxTTL: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.max(315360000)
|
||||||
|
.min(0)
|
||||||
|
.optional()
|
||||||
|
.describe(ALICLOUD_AUTH.UPDATE.accessTokenMaxTTL)
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
|
||||||
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
|
),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identityAliCloudAuth: IdentityAlicloudAuthsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identityAliCloudAuth = await server.services.identityAliCloudAuth.updateAliCloudAuth({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body,
|
||||||
|
identityId: req.params.identityId,
|
||||||
|
allowedArns: req.body.allowedArns
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: identityAliCloudAuth.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_IDENTITY_ALICLOUD_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: identityAliCloudAuth.identityId,
|
||||||
|
allowedArns: identityAliCloudAuth.allowedArns,
|
||||||
|
accessTokenTTL: identityAliCloudAuth.accessTokenTTL,
|
||||||
|
accessTokenMaxTTL: identityAliCloudAuth.accessTokenMaxTTL,
|
||||||
|
accessTokenTrustedIps: identityAliCloudAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
|
||||||
|
accessTokenNumUsesLimit: identityAliCloudAuth.accessTokenNumUsesLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identityAliCloudAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/alicloud-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.AliCloudAuth],
|
||||||
|
description: "Retrieve Alibaba Cloud Auth configuration on identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().describe(ALICLOUD_AUTH.RETRIEVE.identityId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identityAliCloudAuth: IdentityAlicloudAuthsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identityAliCloudAuth = await server.services.identityAliCloudAuth.getAliCloudAuth({
|
||||||
|
identityId: req.params.identityId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: identityAliCloudAuth.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_IDENTITY_ALICLOUD_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: identityAliCloudAuth.identityId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { identityAliCloudAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/alicloud-auth/identities/:identityId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.AliCloudAuth],
|
||||||
|
description: "Delete Alibaba Cloud Auth configuration on identity",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().describe(ALICLOUD_AUTH.REVOKE.identityId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
identityAliCloudAuth: IdentityAlicloudAuthsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const identityAliCloudAuth = await server.services.identityAliCloudAuth.revokeIdentityAliCloudAuth({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
identityId: req.params.identityId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: identityAliCloudAuth.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.REVOKE_IDENTITY_ALICLOUD_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: identityAliCloudAuth.identityId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identityAliCloudAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -108,17 +108,21 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
|||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
.min(1)
|
.min(1)
|
||||||
|
.nullable()
|
||||||
.describe(KUBERNETES_AUTH.ATTACH.kubernetesHost)
|
.describe(KUBERNETES_AUTH.ATTACH.kubernetesHost)
|
||||||
.refine(
|
.refine(
|
||||||
(val) =>
|
(val) => {
|
||||||
characterValidator([
|
if (val === null) return true;
|
||||||
|
|
||||||
|
return characterValidator([
|
||||||
CharacterType.Alphabets,
|
CharacterType.Alphabets,
|
||||||
CharacterType.Numbers,
|
CharacterType.Numbers,
|
||||||
CharacterType.Colon,
|
CharacterType.Colon,
|
||||||
CharacterType.Period,
|
CharacterType.Period,
|
||||||
CharacterType.ForwardSlash,
|
CharacterType.ForwardSlash,
|
||||||
CharacterType.Hyphen
|
CharacterType.Hyphen
|
||||||
])(val),
|
])(val);
|
||||||
|
},
|
||||||
{
|
{
|
||||||
message:
|
message:
|
||||||
"Kubernetes host must only contain alphabets, numbers, colons, periods, hyphen, and forward slashes."
|
"Kubernetes host must only contain alphabets, numbers, colons, periods, hyphen, and forward slashes."
|
||||||
@@ -164,6 +168,13 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
|||||||
.describe(KUBERNETES_AUTH.ATTACH.accessTokenNumUsesLimit)
|
.describe(KUBERNETES_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
|
if (data.tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Api && !data.kubernetesHost) {
|
||||||
|
ctx.addIssue({
|
||||||
|
path: ["kubernetesHost"],
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: "When token review mode is set to API, a Kubernetes host must be provided"
|
||||||
|
});
|
||||||
|
}
|
||||||
if (data.tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Gateway && !data.gatewayId) {
|
if (data.tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Gateway && !data.gatewayId) {
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
path: ["gatewayId"],
|
path: ["gatewayId"],
|
||||||
@@ -171,6 +182,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
|||||||
message: "When token review mode is set to Gateway, a gateway must be selected"
|
message: "When token review mode is set to Gateway, a gateway must be selected"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.accessTokenTTL > data.accessTokenMaxTTL) {
|
if (data.accessTokenTTL > data.accessTokenMaxTTL) {
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
path: ["accessTokenTTL"],
|
path: ["accessTokenTTL"],
|
||||||
@@ -203,7 +215,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
|||||||
type: EventType.ADD_IDENTITY_KUBERNETES_AUTH,
|
type: EventType.ADD_IDENTITY_KUBERNETES_AUTH,
|
||||||
metadata: {
|
metadata: {
|
||||||
identityId: identityKubernetesAuth.identityId,
|
identityId: identityKubernetesAuth.identityId,
|
||||||
kubernetesHost: identityKubernetesAuth.kubernetesHost,
|
kubernetesHost: identityKubernetesAuth.kubernetesHost ?? "",
|
||||||
allowedNamespaces: identityKubernetesAuth.allowedNamespaces,
|
allowedNamespaces: identityKubernetesAuth.allowedNamespaces,
|
||||||
allowedNames: identityKubernetesAuth.allowedNames,
|
allowedNames: identityKubernetesAuth.allowedNames,
|
||||||
accessTokenTTL: identityKubernetesAuth.accessTokenTTL,
|
accessTokenTTL: identityKubernetesAuth.accessTokenTTL,
|
||||||
@@ -243,6 +255,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
|||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
.min(1)
|
.min(1)
|
||||||
|
.nullable()
|
||||||
.optional()
|
.optional()
|
||||||
.describe(KUBERNETES_AUTH.UPDATE.kubernetesHost)
|
.describe(KUBERNETES_AUTH.UPDATE.kubernetesHost)
|
||||||
.refine(
|
.refine(
|
||||||
@@ -345,7 +358,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
|||||||
type: EventType.UPDATE_IDENTITY_KUBENETES_AUTH,
|
type: EventType.UPDATE_IDENTITY_KUBENETES_AUTH,
|
||||||
metadata: {
|
metadata: {
|
||||||
identityId: identityKubernetesAuth.identityId,
|
identityId: identityKubernetesAuth.identityId,
|
||||||
kubernetesHost: identityKubernetesAuth.kubernetesHost,
|
kubernetesHost: identityKubernetesAuth.kubernetesHost ?? "",
|
||||||
allowedNamespaces: identityKubernetesAuth.allowedNamespaces,
|
allowedNamespaces: identityKubernetesAuth.allowedNamespaces,
|
||||||
allowedNames: identityKubernetesAuth.allowedNames,
|
allowedNames: identityKubernetesAuth.allowedNames,
|
||||||
accessTokenTTL: identityKubernetesAuth.accessTokenTTL,
|
accessTokenTTL: identityKubernetesAuth.accessTokenTTL,
|
||||||
|
@@ -15,6 +15,7 @@ import { registerCertRouter } from "./certificate-router";
|
|||||||
import { registerCertificateTemplateRouter } from "./certificate-template-router";
|
import { registerCertificateTemplateRouter } from "./certificate-template-router";
|
||||||
import { registerExternalGroupOrgRoleMappingRouter } from "./external-group-org-role-mapping-router";
|
import { registerExternalGroupOrgRoleMappingRouter } from "./external-group-org-role-mapping-router";
|
||||||
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
|
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
|
||||||
|
import { registerIdentityAliCloudAuthRouter } from "./identity-alicloud-auth-router";
|
||||||
import { registerIdentityAwsAuthRouter } from "./identity-aws-iam-auth-router";
|
import { registerIdentityAwsAuthRouter } from "./identity-aws-iam-auth-router";
|
||||||
import { registerIdentityAzureAuthRouter } from "./identity-azure-auth-router";
|
import { registerIdentityAzureAuthRouter } from "./identity-azure-auth-router";
|
||||||
import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router";
|
import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router";
|
||||||
@@ -63,6 +64,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
|||||||
await authRouter.register(registerIdentityKubernetesRouter);
|
await authRouter.register(registerIdentityKubernetesRouter);
|
||||||
await authRouter.register(registerIdentityGcpAuthRouter);
|
await authRouter.register(registerIdentityGcpAuthRouter);
|
||||||
await authRouter.register(registerIdentityAccessTokenRouter);
|
await authRouter.register(registerIdentityAccessTokenRouter);
|
||||||
|
await authRouter.register(registerIdentityAliCloudAuthRouter);
|
||||||
await authRouter.register(registerIdentityAwsAuthRouter);
|
await authRouter.register(registerIdentityAwsAuthRouter);
|
||||||
await authRouter.register(registerIdentityAzureAuthRouter);
|
await authRouter.register(registerIdentityAzureAuthRouter);
|
||||||
await authRouter.register(registerIdentityOciAuthRouter);
|
await authRouter.register(registerIdentityOciAuthRouter);
|
||||||
|
@@ -0,0 +1,17 @@
|
|||||||
|
import {
|
||||||
|
AzureDevOpsSyncSchema,
|
||||||
|
CreateAzureDevOpsSyncSchema,
|
||||||
|
UpdateAzureDevOpsSyncSchema
|
||||||
|
} from "@app/services/secret-sync/azure-devops";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
|
||||||
|
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
|
||||||
|
|
||||||
|
export const registerAzureDevOpsSyncRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSyncSecretsEndpoints({
|
||||||
|
destination: SecretSync.AzureDevOps,
|
||||||
|
server,
|
||||||
|
responseSchema: AzureDevOpsSyncSchema,
|
||||||
|
createSchema: CreateAzureDevOpsSyncSchema,
|
||||||
|
updateSchema: UpdateAzureDevOpsSyncSchema
|
||||||
|
});
|
@@ -5,6 +5,7 @@ import { registerOnePassSyncRouter } from "./1password-sync-router";
|
|||||||
import { registerAwsParameterStoreSyncRouter } from "./aws-parameter-store-sync-router";
|
import { registerAwsParameterStoreSyncRouter } from "./aws-parameter-store-sync-router";
|
||||||
import { registerAwsSecretsManagerSyncRouter } from "./aws-secrets-manager-sync-router";
|
import { registerAwsSecretsManagerSyncRouter } from "./aws-secrets-manager-sync-router";
|
||||||
import { registerAzureAppConfigurationSyncRouter } from "./azure-app-configuration-sync-router";
|
import { registerAzureAppConfigurationSyncRouter } from "./azure-app-configuration-sync-router";
|
||||||
|
import { registerAzureDevOpsSyncRouter } from "./azure-devops-sync-router";
|
||||||
import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router";
|
import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router";
|
||||||
import { registerCamundaSyncRouter } from "./camunda-sync-router";
|
import { registerCamundaSyncRouter } from "./camunda-sync-router";
|
||||||
import { registerDatabricksSyncRouter } from "./databricks-sync-router";
|
import { registerDatabricksSyncRouter } from "./databricks-sync-router";
|
||||||
@@ -26,6 +27,7 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
|
|||||||
[SecretSync.GCPSecretManager]: registerGcpSyncRouter,
|
[SecretSync.GCPSecretManager]: registerGcpSyncRouter,
|
||||||
[SecretSync.AzureKeyVault]: registerAzureKeyVaultSyncRouter,
|
[SecretSync.AzureKeyVault]: registerAzureKeyVaultSyncRouter,
|
||||||
[SecretSync.AzureAppConfiguration]: registerAzureAppConfigurationSyncRouter,
|
[SecretSync.AzureAppConfiguration]: registerAzureAppConfigurationSyncRouter,
|
||||||
|
[SecretSync.AzureDevOps]: registerAzureDevOpsSyncRouter,
|
||||||
[SecretSync.Databricks]: registerDatabricksSyncRouter,
|
[SecretSync.Databricks]: registerDatabricksSyncRouter,
|
||||||
[SecretSync.Humanitec]: registerHumanitecSyncRouter,
|
[SecretSync.Humanitec]: registerHumanitecSyncRouter,
|
||||||
[SecretSync.TerraformCloud]: registerTerraformCloudSyncRouter,
|
[SecretSync.TerraformCloud]: registerTerraformCloudSyncRouter,
|
||||||
|
@@ -19,6 +19,7 @@ import {
|
|||||||
AzureAppConfigurationSyncListItemSchema,
|
AzureAppConfigurationSyncListItemSchema,
|
||||||
AzureAppConfigurationSyncSchema
|
AzureAppConfigurationSyncSchema
|
||||||
} from "@app/services/secret-sync/azure-app-configuration";
|
} from "@app/services/secret-sync/azure-app-configuration";
|
||||||
|
import { AzureDevOpsSyncListItemSchema, AzureDevOpsSyncSchema } from "@app/services/secret-sync/azure-devops";
|
||||||
import { AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncSchema } from "@app/services/secret-sync/azure-key-vault";
|
import { AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncSchema } from "@app/services/secret-sync/azure-key-vault";
|
||||||
import { CamundaSyncListItemSchema, CamundaSyncSchema } from "@app/services/secret-sync/camunda";
|
import { CamundaSyncListItemSchema, CamundaSyncSchema } from "@app/services/secret-sync/camunda";
|
||||||
import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks";
|
import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks";
|
||||||
@@ -38,6 +39,7 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
|
|||||||
GcpSyncSchema,
|
GcpSyncSchema,
|
||||||
AzureKeyVaultSyncSchema,
|
AzureKeyVaultSyncSchema,
|
||||||
AzureAppConfigurationSyncSchema,
|
AzureAppConfigurationSyncSchema,
|
||||||
|
AzureDevOpsSyncSchema,
|
||||||
DatabricksSyncSchema,
|
DatabricksSyncSchema,
|
||||||
HumanitecSyncSchema,
|
HumanitecSyncSchema,
|
||||||
TerraformCloudSyncSchema,
|
TerraformCloudSyncSchema,
|
||||||
@@ -57,6 +59,7 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
|||||||
GcpSyncListItemSchema,
|
GcpSyncListItemSchema,
|
||||||
AzureKeyVaultSyncListItemSchema,
|
AzureKeyVaultSyncListItemSchema,
|
||||||
AzureAppConfigurationSyncListItemSchema,
|
AzureAppConfigurationSyncListItemSchema,
|
||||||
|
AzureDevOpsSyncListItemSchema,
|
||||||
DatabricksSyncListItemSchema,
|
DatabricksSyncListItemSchema,
|
||||||
HumanitecSyncListItemSchema,
|
HumanitecSyncListItemSchema,
|
||||||
TerraformCloudSyncListItemSchema,
|
TerraformCloudSyncListItemSchema,
|
||||||
|
@@ -7,6 +7,7 @@ export enum AppConnection {
|
|||||||
AzureKeyVault = "azure-key-vault",
|
AzureKeyVault = "azure-key-vault",
|
||||||
AzureAppConfiguration = "azure-app-configuration",
|
AzureAppConfiguration = "azure-app-configuration",
|
||||||
AzureClientSecrets = "azure-client-secrets",
|
AzureClientSecrets = "azure-client-secrets",
|
||||||
|
AzureDevOps = "azure-devops",
|
||||||
Humanitec = "humanitec",
|
Humanitec = "humanitec",
|
||||||
TerraformCloud = "terraform-cloud",
|
TerraformCloud = "terraform-cloud",
|
||||||
Vercel = "vercel",
|
Vercel = "vercel",
|
||||||
@@ -20,6 +21,7 @@ export enum AppConnection {
|
|||||||
LDAP = "ldap",
|
LDAP = "ldap",
|
||||||
TeamCity = "teamcity",
|
TeamCity = "teamcity",
|
||||||
OCI = "oci",
|
OCI = "oci",
|
||||||
|
OracleDB = "oracledb",
|
||||||
OnePass = "1password"
|
OnePass = "1password"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ import {
|
|||||||
OCIConnectionMethod,
|
OCIConnectionMethod,
|
||||||
validateOCIConnectionCredentials
|
validateOCIConnectionCredentials
|
||||||
} from "@app/ee/services/app-connections/oci";
|
} from "@app/ee/services/app-connections/oci";
|
||||||
|
import { getOracleDBConnectionListItem, OracleDBConnectionMethod } from "@app/ee/services/app-connections/oracledb";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { generateHash } from "@app/lib/crypto/encryption";
|
import { generateHash } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
@@ -39,6 +40,11 @@ import {
|
|||||||
getAzureClientSecretsConnectionListItem,
|
getAzureClientSecretsConnectionListItem,
|
||||||
validateAzureClientSecretsConnectionCredentials
|
validateAzureClientSecretsConnectionCredentials
|
||||||
} from "./azure-client-secrets";
|
} from "./azure-client-secrets";
|
||||||
|
import { AzureDevOpsConnectionMethod } from "./azure-devops/azure-devops-enums";
|
||||||
|
import {
|
||||||
|
getAzureDevopsConnectionListItem,
|
||||||
|
validateAzureDevOpsConnectionCredentials
|
||||||
|
} from "./azure-devops/azure-devops-fns";
|
||||||
import {
|
import {
|
||||||
AzureKeyVaultConnectionMethod,
|
AzureKeyVaultConnectionMethod,
|
||||||
getAzureKeyVaultConnectionListItem,
|
getAzureKeyVaultConnectionListItem,
|
||||||
@@ -98,6 +104,7 @@ export const listAppConnectionOptions = () => {
|
|||||||
getGcpConnectionListItem(),
|
getGcpConnectionListItem(),
|
||||||
getAzureKeyVaultConnectionListItem(),
|
getAzureKeyVaultConnectionListItem(),
|
||||||
getAzureAppConfigurationConnectionListItem(),
|
getAzureAppConfigurationConnectionListItem(),
|
||||||
|
getAzureDevopsConnectionListItem(),
|
||||||
getDatabricksConnectionListItem(),
|
getDatabricksConnectionListItem(),
|
||||||
getHumanitecConnectionListItem(),
|
getHumanitecConnectionListItem(),
|
||||||
getTerraformCloudConnectionListItem(),
|
getTerraformCloudConnectionListItem(),
|
||||||
@@ -113,6 +120,7 @@ export const listAppConnectionOptions = () => {
|
|||||||
getLdapConnectionListItem(),
|
getLdapConnectionListItem(),
|
||||||
getTeamCityConnectionListItem(),
|
getTeamCityConnectionListItem(),
|
||||||
getOCIConnectionListItem(),
|
getOCIConnectionListItem(),
|
||||||
|
getOracleDBConnectionListItem(),
|
||||||
getOnePassConnectionListItem()
|
getOnePassConnectionListItem()
|
||||||
].sort((a, b) => a.name.localeCompare(b.name));
|
].sort((a, b) => a.name.localeCompare(b.name));
|
||||||
};
|
};
|
||||||
@@ -173,6 +181,7 @@ export const validateAppConnectionCredentials = async (
|
|||||||
validateAzureAppConfigurationConnectionCredentials as TAppConnectionCredentialsValidator,
|
validateAzureAppConfigurationConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.AzureClientSecrets]:
|
[AppConnection.AzureClientSecrets]:
|
||||||
validateAzureClientSecretsConnectionCredentials as TAppConnectionCredentialsValidator,
|
validateAzureClientSecretsConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
|
[AppConnection.AzureDevOps]: validateAzureDevOpsConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Humanitec]: validateHumanitecConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Humanitec]: validateHumanitecConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Postgres]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Postgres]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
@@ -186,6 +195,7 @@ export const validateAppConnectionCredentials = async (
|
|||||||
[AppConnection.LDAP]: validateLdapConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.LDAP]: validateLdapConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.TeamCity]: validateTeamCityConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.TeamCity]: validateTeamCityConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.OCI]: validateOCIConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.OCI]: validateOCIConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
|
[AppConnection.OracleDB]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.OnePass]: validateOnePassConnectionCredentials as TAppConnectionCredentialsValidator
|
[AppConnection.OnePass]: validateOnePassConnectionCredentials as TAppConnectionCredentialsValidator
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -201,6 +211,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
|||||||
case AzureAppConfigurationConnectionMethod.OAuth:
|
case AzureAppConfigurationConnectionMethod.OAuth:
|
||||||
case AzureClientSecretsConnectionMethod.OAuth:
|
case AzureClientSecretsConnectionMethod.OAuth:
|
||||||
case GitHubConnectionMethod.OAuth:
|
case GitHubConnectionMethod.OAuth:
|
||||||
|
case AzureDevOpsConnectionMethod.OAuth:
|
||||||
return "OAuth";
|
return "OAuth";
|
||||||
case AwsConnectionMethod.AccessKey:
|
case AwsConnectionMethod.AccessKey:
|
||||||
case OCIConnectionMethod.AccessKey:
|
case OCIConnectionMethod.AccessKey:
|
||||||
@@ -221,10 +232,12 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
|||||||
case PostgresConnectionMethod.UsernameAndPassword:
|
case PostgresConnectionMethod.UsernameAndPassword:
|
||||||
case MsSqlConnectionMethod.UsernameAndPassword:
|
case MsSqlConnectionMethod.UsernameAndPassword:
|
||||||
case MySqlConnectionMethod.UsernameAndPassword:
|
case MySqlConnectionMethod.UsernameAndPassword:
|
||||||
|
case OracleDBConnectionMethod.UsernameAndPassword:
|
||||||
return "Username & Password";
|
return "Username & Password";
|
||||||
case WindmillConnectionMethod.AccessToken:
|
case WindmillConnectionMethod.AccessToken:
|
||||||
case HCVaultConnectionMethod.AccessToken:
|
case HCVaultConnectionMethod.AccessToken:
|
||||||
case TeamCityConnectionMethod.AccessToken:
|
case TeamCityConnectionMethod.AccessToken:
|
||||||
|
case AzureDevOpsConnectionMethod.AccessToken:
|
||||||
return "Access Token";
|
return "Access Token";
|
||||||
case Auth0ConnectionMethod.ClientCredentials:
|
case Auth0ConnectionMethod.ClientCredentials:
|
||||||
return "Client Credentials";
|
return "Client Credentials";
|
||||||
@@ -270,6 +283,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
|
|||||||
[AppConnection.GCP]: platformManagedCredentialsNotSupported,
|
[AppConnection.GCP]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.AzureKeyVault]: platformManagedCredentialsNotSupported,
|
[AppConnection.AzureKeyVault]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.AzureAppConfiguration]: platformManagedCredentialsNotSupported,
|
[AppConnection.AzureAppConfiguration]: platformManagedCredentialsNotSupported,
|
||||||
|
[AppConnection.AzureDevOps]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Humanitec]: platformManagedCredentialsNotSupported,
|
[AppConnection.Humanitec]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Postgres]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
|
[AppConnection.Postgres]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
|
||||||
[AppConnection.MsSql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
|
[AppConnection.MsSql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
|
||||||
@@ -284,6 +298,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
|
|||||||
[AppConnection.LDAP]: platformManagedCredentialsNotSupported, // we could support this in the future
|
[AppConnection.LDAP]: platformManagedCredentialsNotSupported, // we could support this in the future
|
||||||
[AppConnection.TeamCity]: platformManagedCredentialsNotSupported,
|
[AppConnection.TeamCity]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.OCI]: platformManagedCredentialsNotSupported,
|
[AppConnection.OCI]: platformManagedCredentialsNotSupported,
|
||||||
|
[AppConnection.OracleDB]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
|
||||||
[AppConnection.OnePass]: platformManagedCredentialsNotSupported
|
[AppConnection.OnePass]: platformManagedCredentialsNotSupported
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -8,6 +8,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
|||||||
[AppConnection.AzureKeyVault]: "Azure Key Vault",
|
[AppConnection.AzureKeyVault]: "Azure Key Vault",
|
||||||
[AppConnection.AzureAppConfiguration]: "Azure App Configuration",
|
[AppConnection.AzureAppConfiguration]: "Azure App Configuration",
|
||||||
[AppConnection.AzureClientSecrets]: "Azure Client Secrets",
|
[AppConnection.AzureClientSecrets]: "Azure Client Secrets",
|
||||||
|
[AppConnection.AzureDevOps]: "Azure DevOps",
|
||||||
[AppConnection.Databricks]: "Databricks",
|
[AppConnection.Databricks]: "Databricks",
|
||||||
[AppConnection.Humanitec]: "Humanitec",
|
[AppConnection.Humanitec]: "Humanitec",
|
||||||
[AppConnection.TerraformCloud]: "Terraform Cloud",
|
[AppConnection.TerraformCloud]: "Terraform Cloud",
|
||||||
@@ -22,6 +23,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
|||||||
[AppConnection.LDAP]: "LDAP",
|
[AppConnection.LDAP]: "LDAP",
|
||||||
[AppConnection.TeamCity]: "TeamCity",
|
[AppConnection.TeamCity]: "TeamCity",
|
||||||
[AppConnection.OCI]: "OCI",
|
[AppConnection.OCI]: "OCI",
|
||||||
|
[AppConnection.OracleDB]: "OracleDB",
|
||||||
[AppConnection.OnePass]: "1Password"
|
[AppConnection.OnePass]: "1Password"
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -33,6 +35,7 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
|
|||||||
[AppConnection.AzureKeyVault]: AppConnectionPlanType.Regular,
|
[AppConnection.AzureKeyVault]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.AzureAppConfiguration]: AppConnectionPlanType.Regular,
|
[AppConnection.AzureAppConfiguration]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.AzureClientSecrets]: AppConnectionPlanType.Regular,
|
[AppConnection.AzureClientSecrets]: AppConnectionPlanType.Regular,
|
||||||
|
[AppConnection.AzureDevOps]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.Databricks]: AppConnectionPlanType.Regular,
|
[AppConnection.Databricks]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.Humanitec]: AppConnectionPlanType.Regular,
|
[AppConnection.Humanitec]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.TerraformCloud]: AppConnectionPlanType.Regular,
|
[AppConnection.TerraformCloud]: AppConnectionPlanType.Regular,
|
||||||
@@ -46,6 +49,7 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
|
|||||||
[AppConnection.LDAP]: AppConnectionPlanType.Regular,
|
[AppConnection.LDAP]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.TeamCity]: AppConnectionPlanType.Regular,
|
[AppConnection.TeamCity]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.OCI]: AppConnectionPlanType.Enterprise,
|
[AppConnection.OCI]: AppConnectionPlanType.Enterprise,
|
||||||
|
[AppConnection.OracleDB]: AppConnectionPlanType.Enterprise,
|
||||||
[AppConnection.OnePass]: AppConnectionPlanType.Regular,
|
[AppConnection.OnePass]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.MySql]: AppConnectionPlanType.Regular
|
[AppConnection.MySql]: AppConnectionPlanType.Regular
|
||||||
};
|
};
|
||||||
|
@@ -2,6 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
|
|||||||
|
|
||||||
import { ValidateOCIConnectionCredentialsSchema } from "@app/ee/services/app-connections/oci";
|
import { ValidateOCIConnectionCredentialsSchema } from "@app/ee/services/app-connections/oci";
|
||||||
import { ociConnectionService } from "@app/ee/services/app-connections/oci/oci-connection-service";
|
import { ociConnectionService } from "@app/ee/services/app-connections/oci/oci-connection-service";
|
||||||
|
import { ValidateOracleDBConnectionCredentialsSchema } from "@app/ee/services/app-connections/oracledb";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionAppConnectionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionAppConnectionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
@@ -41,6 +42,8 @@ import { awsConnectionService } from "./aws/aws-connection-service";
|
|||||||
import { ValidateAzureAppConfigurationConnectionCredentialsSchema } from "./azure-app-configuration";
|
import { ValidateAzureAppConfigurationConnectionCredentialsSchema } from "./azure-app-configuration";
|
||||||
import { ValidateAzureClientSecretsConnectionCredentialsSchema } from "./azure-client-secrets";
|
import { ValidateAzureClientSecretsConnectionCredentialsSchema } from "./azure-client-secrets";
|
||||||
import { azureClientSecretsConnectionService } from "./azure-client-secrets/azure-client-secrets-service";
|
import { azureClientSecretsConnectionService } from "./azure-client-secrets/azure-client-secrets-service";
|
||||||
|
import { ValidateAzureDevOpsConnectionCredentialsSchema } from "./azure-devops/azure-devops-schemas";
|
||||||
|
import { azureDevOpsConnectionService } from "./azure-devops/azure-devops-service";
|
||||||
import { ValidateAzureKeyVaultConnectionCredentialsSchema } from "./azure-key-vault";
|
import { ValidateAzureKeyVaultConnectionCredentialsSchema } from "./azure-key-vault";
|
||||||
import { ValidateCamundaConnectionCredentialsSchema } from "./camunda";
|
import { ValidateCamundaConnectionCredentialsSchema } from "./camunda";
|
||||||
import { camundaConnectionService } from "./camunda/camunda-connection-service";
|
import { camundaConnectionService } from "./camunda/camunda-connection-service";
|
||||||
@@ -84,6 +87,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
|||||||
[AppConnection.GCP]: ValidateGcpConnectionCredentialsSchema,
|
[AppConnection.GCP]: ValidateGcpConnectionCredentialsSchema,
|
||||||
[AppConnection.AzureKeyVault]: ValidateAzureKeyVaultConnectionCredentialsSchema,
|
[AppConnection.AzureKeyVault]: ValidateAzureKeyVaultConnectionCredentialsSchema,
|
||||||
[AppConnection.AzureAppConfiguration]: ValidateAzureAppConfigurationConnectionCredentialsSchema,
|
[AppConnection.AzureAppConfiguration]: ValidateAzureAppConfigurationConnectionCredentialsSchema,
|
||||||
|
[AppConnection.AzureDevOps]: ValidateAzureDevOpsConnectionCredentialsSchema,
|
||||||
[AppConnection.Databricks]: ValidateDatabricksConnectionCredentialsSchema,
|
[AppConnection.Databricks]: ValidateDatabricksConnectionCredentialsSchema,
|
||||||
[AppConnection.Humanitec]: ValidateHumanitecConnectionCredentialsSchema,
|
[AppConnection.Humanitec]: ValidateHumanitecConnectionCredentialsSchema,
|
||||||
[AppConnection.TerraformCloud]: ValidateTerraformCloudConnectionCredentialsSchema,
|
[AppConnection.TerraformCloud]: ValidateTerraformCloudConnectionCredentialsSchema,
|
||||||
@@ -99,6 +103,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
|||||||
[AppConnection.LDAP]: ValidateLdapConnectionCredentialsSchema,
|
[AppConnection.LDAP]: ValidateLdapConnectionCredentialsSchema,
|
||||||
[AppConnection.TeamCity]: ValidateTeamCityConnectionCredentialsSchema,
|
[AppConnection.TeamCity]: ValidateTeamCityConnectionCredentialsSchema,
|
||||||
[AppConnection.OCI]: ValidateOCIConnectionCredentialsSchema,
|
[AppConnection.OCI]: ValidateOCIConnectionCredentialsSchema,
|
||||||
|
[AppConnection.OracleDB]: ValidateOracleDBConnectionCredentialsSchema,
|
||||||
[AppConnection.OnePass]: ValidateOnePassConnectionCredentialsSchema
|
[AppConnection.OnePass]: ValidateOnePassConnectionCredentialsSchema
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -498,6 +503,7 @@ export const appConnectionServiceFactory = ({
|
|||||||
camunda: camundaConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
camunda: camundaConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||||
vercel: vercelConnectionService(connectAppConnectionById),
|
vercel: vercelConnectionService(connectAppConnectionById),
|
||||||
azureClientSecrets: azureClientSecretsConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
azureClientSecrets: azureClientSecretsConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||||
|
azureDevOps: azureDevOpsConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||||
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||||
hcvault: hcVaultConnectionService(connectAppConnectionById),
|
hcvault: hcVaultConnectionService(connectAppConnectionById),
|
||||||
windmill: windmillConnectionService(connectAppConnectionById),
|
windmill: windmillConnectionService(connectAppConnectionById),
|
||||||
|
@@ -4,6 +4,11 @@ import {
|
|||||||
TOCIConnectionInput,
|
TOCIConnectionInput,
|
||||||
TValidateOCIConnectionCredentialsSchema
|
TValidateOCIConnectionCredentialsSchema
|
||||||
} from "@app/ee/services/app-connections/oci";
|
} from "@app/ee/services/app-connections/oci";
|
||||||
|
import {
|
||||||
|
TOracleDBConnection,
|
||||||
|
TOracleDBConnectionInput,
|
||||||
|
TValidateOracleDBConnectionCredentialsSchema
|
||||||
|
} from "@app/ee/services/app-connections/oracledb";
|
||||||
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||||
import { TSqlConnectionConfig } from "@app/services/app-connection/shared/sql/sql-connection-types";
|
import { TSqlConnectionConfig } from "@app/services/app-connection/shared/sql/sql-connection-types";
|
||||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
@@ -39,6 +44,12 @@ import {
|
|||||||
TAzureClientSecretsConnectionInput,
|
TAzureClientSecretsConnectionInput,
|
||||||
TValidateAzureClientSecretsConnectionCredentialsSchema
|
TValidateAzureClientSecretsConnectionCredentialsSchema
|
||||||
} from "./azure-client-secrets";
|
} from "./azure-client-secrets";
|
||||||
|
import {
|
||||||
|
TAzureDevOpsConnection,
|
||||||
|
TAzureDevOpsConnectionConfig,
|
||||||
|
TAzureDevOpsConnectionInput,
|
||||||
|
TValidateAzureDevOpsConnectionCredentialsSchema
|
||||||
|
} from "./azure-devops/azure-devops-types";
|
||||||
import {
|
import {
|
||||||
TAzureKeyVaultConnection,
|
TAzureKeyVaultConnection,
|
||||||
TAzureKeyVaultConnectionConfig,
|
TAzureKeyVaultConnectionConfig,
|
||||||
@@ -132,6 +143,7 @@ export type TAppConnection = { id: string } & (
|
|||||||
| TGcpConnection
|
| TGcpConnection
|
||||||
| TAzureKeyVaultConnection
|
| TAzureKeyVaultConnection
|
||||||
| TAzureAppConfigurationConnection
|
| TAzureAppConfigurationConnection
|
||||||
|
| TAzureDevOpsConnection
|
||||||
| TDatabricksConnection
|
| TDatabricksConnection
|
||||||
| THumanitecConnection
|
| THumanitecConnection
|
||||||
| TTerraformCloudConnection
|
| TTerraformCloudConnection
|
||||||
@@ -147,12 +159,13 @@ export type TAppConnection = { id: string } & (
|
|||||||
| TLdapConnection
|
| TLdapConnection
|
||||||
| TTeamCityConnection
|
| TTeamCityConnection
|
||||||
| TOCIConnection
|
| TOCIConnection
|
||||||
|
| TOracleDBConnection
|
||||||
| TOnePassConnection
|
| TOnePassConnection
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
|
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
|
||||||
|
|
||||||
export type TSqlConnection = TPostgresConnection | TMsSqlConnection | TMySqlConnection;
|
export type TSqlConnection = TPostgresConnection | TMsSqlConnection | TMySqlConnection | TOracleDBConnection;
|
||||||
|
|
||||||
export type TAppConnectionInput = { id: string } & (
|
export type TAppConnectionInput = { id: string } & (
|
||||||
| TAwsConnectionInput
|
| TAwsConnectionInput
|
||||||
@@ -161,6 +174,7 @@ export type TAppConnectionInput = { id: string } & (
|
|||||||
| TGcpConnectionInput
|
| TGcpConnectionInput
|
||||||
| TAzureKeyVaultConnectionInput
|
| TAzureKeyVaultConnectionInput
|
||||||
| TAzureAppConfigurationConnectionInput
|
| TAzureAppConfigurationConnectionInput
|
||||||
|
| TAzureDevOpsConnectionInput
|
||||||
| TDatabricksConnectionInput
|
| TDatabricksConnectionInput
|
||||||
| THumanitecConnectionInput
|
| THumanitecConnectionInput
|
||||||
| TTerraformCloudConnectionInput
|
| TTerraformCloudConnectionInput
|
||||||
@@ -176,10 +190,15 @@ export type TAppConnectionInput = { id: string } & (
|
|||||||
| TLdapConnectionInput
|
| TLdapConnectionInput
|
||||||
| TTeamCityConnectionInput
|
| TTeamCityConnectionInput
|
||||||
| TOCIConnectionInput
|
| TOCIConnectionInput
|
||||||
|
| TOracleDBConnectionInput
|
||||||
| TOnePassConnectionInput
|
| TOnePassConnectionInput
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TSqlConnectionInput = TPostgresConnectionInput | TMsSqlConnectionInput | TMySqlConnectionInput;
|
export type TSqlConnectionInput =
|
||||||
|
| TPostgresConnectionInput
|
||||||
|
| TMsSqlConnectionInput
|
||||||
|
| TMySqlConnectionInput
|
||||||
|
| TOracleDBConnectionInput;
|
||||||
|
|
||||||
export type TCreateAppConnectionDTO = Pick<
|
export type TCreateAppConnectionDTO = Pick<
|
||||||
TAppConnectionInput,
|
TAppConnectionInput,
|
||||||
@@ -197,6 +216,7 @@ export type TAppConnectionConfig =
|
|||||||
| TGcpConnectionConfig
|
| TGcpConnectionConfig
|
||||||
| TAzureKeyVaultConnectionConfig
|
| TAzureKeyVaultConnectionConfig
|
||||||
| TAzureAppConfigurationConnectionConfig
|
| TAzureAppConfigurationConnectionConfig
|
||||||
|
| TAzureDevOpsConnectionConfig
|
||||||
| TAzureClientSecretsConnectionConfig
|
| TAzureClientSecretsConnectionConfig
|
||||||
| TDatabricksConnectionConfig
|
| TDatabricksConnectionConfig
|
||||||
| THumanitecConnectionConfig
|
| THumanitecConnectionConfig
|
||||||
@@ -220,6 +240,7 @@ export type TValidateAppConnectionCredentialsSchema =
|
|||||||
| TValidateAzureKeyVaultConnectionCredentialsSchema
|
| TValidateAzureKeyVaultConnectionCredentialsSchema
|
||||||
| TValidateAzureAppConfigurationConnectionCredentialsSchema
|
| TValidateAzureAppConfigurationConnectionCredentialsSchema
|
||||||
| TValidateAzureClientSecretsConnectionCredentialsSchema
|
| TValidateAzureClientSecretsConnectionCredentialsSchema
|
||||||
|
| TValidateAzureDevOpsConnectionCredentialsSchema
|
||||||
| TValidateDatabricksConnectionCredentialsSchema
|
| TValidateDatabricksConnectionCredentialsSchema
|
||||||
| TValidateHumanitecConnectionCredentialsSchema
|
| TValidateHumanitecConnectionCredentialsSchema
|
||||||
| TValidatePostgresConnectionCredentialsSchema
|
| TValidatePostgresConnectionCredentialsSchema
|
||||||
@@ -234,6 +255,7 @@ export type TValidateAppConnectionCredentialsSchema =
|
|||||||
| TValidateLdapConnectionCredentialsSchema
|
| TValidateLdapConnectionCredentialsSchema
|
||||||
| TValidateTeamCityConnectionCredentialsSchema
|
| TValidateTeamCityConnectionCredentialsSchema
|
||||||
| TValidateOCIConnectionCredentialsSchema
|
| TValidateOCIConnectionCredentialsSchema
|
||||||
|
| TValidateOracleDBConnectionCredentialsSchema
|
||||||
| TValidateOnePassConnectionCredentialsSchema;
|
| TValidateOnePassConnectionCredentialsSchema;
|
||||||
|
|
||||||
export type TListAwsConnectionKmsKeys = {
|
export type TListAwsConnectionKmsKeys = {
|
||||||
|
@@ -0,0 +1,4 @@
|
|||||||
|
export enum AzureDevOpsConnectionMethod {
|
||||||
|
OAuth = "oauth",
|
||||||
|
AccessToken = "access-token"
|
||||||
|
}
|
@@ -0,0 +1,269 @@
|
|||||||
|
/* eslint-disable no-case-declarations */
|
||||||
|
import { AxiosError, AxiosResponse } from "axios";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import {
|
||||||
|
decryptAppConnectionCredentials,
|
||||||
|
encryptAppConnectionCredentials,
|
||||||
|
getAppConnectionMethodName
|
||||||
|
} from "@app/services/app-connection/app-connection-fns";
|
||||||
|
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
|
||||||
|
import { TAppConnectionDALFactory } from "../app-connection-dal";
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import { AzureDevOpsConnectionMethod } from "./azure-devops-enums";
|
||||||
|
import {
|
||||||
|
ExchangeCodeAzureResponse,
|
||||||
|
TAzureDevOpsConnectionConfig,
|
||||||
|
TAzureDevOpsConnectionCredentials
|
||||||
|
} from "./azure-devops-types";
|
||||||
|
|
||||||
|
export const getAzureDevopsConnectionListItem = () => {
|
||||||
|
const { INF_APP_CONNECTION_AZURE_CLIENT_ID } = getConfig();
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "Azure DevOps" as const,
|
||||||
|
app: AppConnection.AzureDevOps as const,
|
||||||
|
methods: Object.values(AzureDevOpsConnectionMethod) as [
|
||||||
|
AzureDevOpsConnectionMethod.OAuth,
|
||||||
|
AzureDevOpsConnectionMethod.AccessToken
|
||||||
|
],
|
||||||
|
oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_ID
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAzureDevopsConnection = async (
|
||||||
|
connectionId: string,
|
||||||
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "updateById">,
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||||
|
) => {
|
||||||
|
const appConnection = await appConnectionDAL.findById(connectionId);
|
||||||
|
|
||||||
|
if (!appConnection) {
|
||||||
|
throw new NotFoundError({ message: `Connection with ID '${connectionId}' not found` });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appConnection.app !== AppConnection.AzureDevOps) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Connection with ID '${connectionId}' is not an Azure DevOps connection`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const credentials = (await decryptAppConnectionCredentials({
|
||||||
|
orgId: appConnection.orgId,
|
||||||
|
kmsService,
|
||||||
|
encryptedCredentials: appConnection.encryptedCredentials
|
||||||
|
})) as TAzureDevOpsConnectionCredentials;
|
||||||
|
|
||||||
|
// Handle different connection methods
|
||||||
|
switch (appConnection.method) {
|
||||||
|
case AzureDevOpsConnectionMethod.OAuth:
|
||||||
|
const appCfg = getConfig();
|
||||||
|
if (!appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID || !appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Azure environment variables have not been configured`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!("refreshToken" in credentials)) {
|
||||||
|
throw new BadRequestError({ message: "Invalid OAuth credentials" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { refreshToken, tenantId } = credentials;
|
||||||
|
const currentTime = Date.now();
|
||||||
|
|
||||||
|
const { data } = await request.post<ExchangeCodeAzureResponse>(
|
||||||
|
IntegrationUrls.AZURE_TOKEN_URL.replace("common", tenantId || "common"),
|
||||||
|
new URLSearchParams({
|
||||||
|
grant_type: "refresh_token",
|
||||||
|
scope: `https://app.vssps.visualstudio.com/.default`,
|
||||||
|
client_id: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID,
|
||||||
|
client_secret: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET,
|
||||||
|
refresh_token: refreshToken
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedCredentials = {
|
||||||
|
...credentials,
|
||||||
|
accessToken: data.access_token,
|
||||||
|
expiresAt: currentTime + data.expires_in * 1000,
|
||||||
|
refreshToken: data.refresh_token
|
||||||
|
};
|
||||||
|
|
||||||
|
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||||
|
credentials: updatedCredentials,
|
||||||
|
orgId: appConnection.orgId,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials });
|
||||||
|
|
||||||
|
return data.access_token;
|
||||||
|
|
||||||
|
case AzureDevOpsConnectionMethod.AccessToken:
|
||||||
|
if (!("accessToken" in credentials)) {
|
||||||
|
throw new BadRequestError({ message: "Invalid API token credentials" });
|
||||||
|
}
|
||||||
|
// For access token, return the basic auth token directly
|
||||||
|
return credentials.accessToken;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new BadRequestError({ message: `Unsupported connection method` });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateAzureDevOpsConnectionCredentials = async (config: TAzureDevOpsConnectionConfig) => {
|
||||||
|
const { credentials: inputCredentials, method } = config;
|
||||||
|
|
||||||
|
const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRET, SITE_URL } = getConfig();
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case AzureDevOpsConnectionMethod.OAuth:
|
||||||
|
if (!SITE_URL) {
|
||||||
|
throw new InternalServerError({ message: "SITE_URL env var is required to complete Azure OAuth flow" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!INF_APP_CONNECTION_AZURE_CLIENT_ID || !INF_APP_CONNECTION_AZURE_CLIENT_SECRET) {
|
||||||
|
throw new InternalServerError({
|
||||||
|
message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let tokenResp: AxiosResponse<ExchangeCodeAzureResponse> | null = null;
|
||||||
|
let tokenError: AxiosError | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const oauthCredentials = inputCredentials as { code: string; tenantId: string };
|
||||||
|
tokenResp = await request.post<ExchangeCodeAzureResponse>(
|
||||||
|
IntegrationUrls.AZURE_TOKEN_URL.replace("common", oauthCredentials.tenantId || "common"),
|
||||||
|
new URLSearchParams({
|
||||||
|
grant_type: "authorization_code",
|
||||||
|
code: oauthCredentials.code,
|
||||||
|
scope: `https://app.vssps.visualstudio.com/.default`,
|
||||||
|
client_id: INF_APP_CONNECTION_AZURE_CLIENT_ID,
|
||||||
|
client_secret: INF_APP_CONNECTION_AZURE_CLIENT_SECRET,
|
||||||
|
redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback`
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
if (e instanceof AxiosError) {
|
||||||
|
tokenError = e;
|
||||||
|
} else {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Unable to validate connection: verify credentials`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokenError) {
|
||||||
|
if (tokenError instanceof AxiosError) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to get access token: ${
|
||||||
|
(tokenError?.response?.data as { error_description?: string })?.error_description || "Unknown error"
|
||||||
|
}`
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new InternalServerError({
|
||||||
|
message: "Failed to get access token"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tokenResp) {
|
||||||
|
throw new InternalServerError({
|
||||||
|
message: `Failed to get access token: Token was empty with no error`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const oauthCredentials = inputCredentials as { code: string; tenantId: string; orgName: string };
|
||||||
|
return {
|
||||||
|
tenantId: oauthCredentials.tenantId,
|
||||||
|
orgName: oauthCredentials.orgName,
|
||||||
|
accessToken: tokenResp.data.access_token,
|
||||||
|
refreshToken: tokenResp.data.refresh_token,
|
||||||
|
expiresAt: Date.now() + tokenResp.data.expires_in * 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
case AzureDevOpsConnectionMethod.AccessToken:
|
||||||
|
const accessTokenCredentials = inputCredentials as { accessToken: string; orgName?: string };
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (accessTokenCredentials.orgName) {
|
||||||
|
// Validate against specific organization
|
||||||
|
const response = await request.get(
|
||||||
|
`${IntegrationUrls.AZURE_DEVOPS_API_URL}/${encodeURIComponent(accessTokenCredentials.orgName)}/_apis/projects?api-version=7.2-preview.2&$top=1`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Basic ${Buffer.from(`:${accessTokenCredentials.accessToken}`).toString("base64")}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to validate connection: ${response.status}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken: accessTokenCredentials.accessToken,
|
||||||
|
orgName: accessTokenCredentials.orgName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Validate via profile and discover organizations
|
||||||
|
const profileResponse = await request.get<{ displayName: string }>(
|
||||||
|
`https://app.vssps.visualstudio.com/_apis/profile/profiles/me?api-version=7.1`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Basic ${Buffer.from(`:${accessTokenCredentials.accessToken}`).toString("base64")}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let organizations: Array<{ accountId: string; accountName: string; accountUri: string }> = [];
|
||||||
|
try {
|
||||||
|
const orgsResponse = await request.get<{
|
||||||
|
value: Array<{ accountId: string; accountName: string; accountUri: string }>;
|
||||||
|
}>(`https://app.vssps.visualstudio.com/_apis/accounts?api-version=7.1`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Basic ${Buffer.from(`:${accessTokenCredentials.accessToken}`).toString("base64")}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
organizations = orgsResponse.data.value || [];
|
||||||
|
} catch (orgError) {
|
||||||
|
logger.warn(orgError, "Could not fetch organizations automatically:");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken: accessTokenCredentials.accessToken,
|
||||||
|
userDisplayName: profileResponse.data.displayName,
|
||||||
|
organizations: organizations.map((org) => ({
|
||||||
|
accountId: org.accountId,
|
||||||
|
accountName: org.accountName,
|
||||||
|
accountUri: org.accountUri
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
const errorMessage = accessTokenCredentials.orgName
|
||||||
|
? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
`Failed to validate access token for organization '${accessTokenCredentials.orgName}': ${error.response?.data?.message || error.message}`
|
||||||
|
: `Invalid Azure DevOps Personal Access Token: ${error.response?.status === 401 ? "Token is invalid or expired" : error.message}`;
|
||||||
|
|
||||||
|
throw new BadRequestError({ message: errorMessage });
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Unable to validate Azure DevOps token`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InternalServerError({
|
||||||
|
message: `Unhandled Azure connection method: ${method as AzureDevOpsConnectionMethod}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@@ -0,0 +1,112 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { AppConnections } from "@app/lib/api-docs";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
BaseAppConnectionSchema,
|
||||||
|
GenericCreateAppConnectionFieldsSchema,
|
||||||
|
GenericUpdateAppConnectionFieldsSchema
|
||||||
|
} from "@app/services/app-connection/app-connection-schemas";
|
||||||
|
|
||||||
|
import { AzureDevOpsConnectionMethod } from "./azure-devops-enums";
|
||||||
|
|
||||||
|
export const AzureDevOpsConnectionOAuthInputCredentialsSchema = z.object({
|
||||||
|
code: z.string().trim().min(1, "OAuth code required").describe(AppConnections.CREDENTIALS.AZURE_DEVOPS.code),
|
||||||
|
tenantId: z.string().trim().min(1, "Tenant ID required").describe(AppConnections.CREDENTIALS.AZURE_DEVOPS.tenantId),
|
||||||
|
orgName: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "Organization name required")
|
||||||
|
.describe(AppConnections.CREDENTIALS.AZURE_DEVOPS.orgName)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureDevOpsConnectionOAuthOutputCredentialsSchema = z.object({
|
||||||
|
tenantId: z.string(),
|
||||||
|
orgName: z.string(),
|
||||||
|
accessToken: z.string(),
|
||||||
|
refreshToken: z.string(),
|
||||||
|
expiresAt: z.number()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureDevOpsConnectionAccessTokenInputCredentialsSchema = z.object({
|
||||||
|
accessToken: z.string().trim().min(1, "Access Token required"),
|
||||||
|
orgName: z.string().trim().min(1, "Organization name required")
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureDevOpsConnectionAccessTokenOutputCredentialsSchema = z.object({
|
||||||
|
accessToken: z.string(),
|
||||||
|
orgName: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ValidateAzureDevOpsConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||||
|
z.object({
|
||||||
|
method: z
|
||||||
|
.literal(AzureDevOpsConnectionMethod.OAuth)
|
||||||
|
.describe(AppConnections.CREATE(AppConnection.AzureDevOps).method),
|
||||||
|
credentials: AzureDevOpsConnectionOAuthInputCredentialsSchema.describe(
|
||||||
|
AppConnections.CREATE(AppConnection.AzureDevOps).credentials
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
method: z
|
||||||
|
.literal(AzureDevOpsConnectionMethod.AccessToken)
|
||||||
|
.describe(AppConnections.CREATE(AppConnection.AzureDevOps).method),
|
||||||
|
credentials: AzureDevOpsConnectionAccessTokenInputCredentialsSchema.describe(
|
||||||
|
AppConnections.CREATE(AppConnection.AzureDevOps).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const CreateAzureDevOpsConnectionSchema = ValidateAzureDevOpsConnectionCredentialsSchema.and(
|
||||||
|
GenericCreateAppConnectionFieldsSchema(AppConnection.AzureDevOps)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const UpdateAzureDevOpsConnectionSchema = z
|
||||||
|
.object({
|
||||||
|
credentials: z
|
||||||
|
.union([AzureDevOpsConnectionOAuthInputCredentialsSchema, AzureDevOpsConnectionAccessTokenInputCredentialsSchema])
|
||||||
|
.optional()
|
||||||
|
.describe(AppConnections.UPDATE(AppConnection.AzureDevOps).credentials)
|
||||||
|
})
|
||||||
|
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.AzureDevOps));
|
||||||
|
|
||||||
|
const BaseAzureDevOpsConnectionSchema = BaseAppConnectionSchema.extend({
|
||||||
|
app: z.literal(AppConnection.AzureDevOps)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureDevOpsConnectionSchema = z.intersection(
|
||||||
|
BaseAzureDevOpsConnectionSchema,
|
||||||
|
z.discriminatedUnion("method", [
|
||||||
|
z.object({
|
||||||
|
method: z.literal(AzureDevOpsConnectionMethod.OAuth),
|
||||||
|
credentials: AzureDevOpsConnectionOAuthOutputCredentialsSchema
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
method: z.literal(AzureDevOpsConnectionMethod.AccessToken),
|
||||||
|
credentials: AzureDevOpsConnectionAccessTokenOutputCredentialsSchema
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
export const SanitizedAzureDevOpsConnectionSchema = z.discriminatedUnion("method", [
|
||||||
|
BaseAzureDevOpsConnectionSchema.extend({
|
||||||
|
method: z.literal(AzureDevOpsConnectionMethod.OAuth),
|
||||||
|
credentials: AzureDevOpsConnectionOAuthOutputCredentialsSchema.pick({
|
||||||
|
tenantId: true,
|
||||||
|
orgName: true
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
BaseAzureDevOpsConnectionSchema.extend({
|
||||||
|
method: z.literal(AzureDevOpsConnectionMethod.AccessToken),
|
||||||
|
credentials: AzureDevOpsConnectionAccessTokenOutputCredentialsSchema.pick({
|
||||||
|
orgName: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const AzureDevOpsConnectionListItemSchema = z.object({
|
||||||
|
name: z.literal("Azure DevOps"),
|
||||||
|
app: z.literal(AppConnection.AzureDevOps),
|
||||||
|
methods: z.nativeEnum(AzureDevOpsConnectionMethod).array(),
|
||||||
|
oauthClientId: z.string().optional()
|
||||||
|
});
|
@@ -0,0 +1,127 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
|
/* eslint-disable no-case-declarations */
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
|
||||||
|
import { AzureDevOpsConnectionMethod } from "./azure-devops-enums";
|
||||||
|
import { getAzureDevopsConnection } from "./azure-devops-fns";
|
||||||
|
import { TAzureDevOpsConnection } from "./azure-devops-types";
|
||||||
|
|
||||||
|
type TGetAppConnectionFunc = (
|
||||||
|
app: AppConnection,
|
||||||
|
connectionId: string,
|
||||||
|
actor: OrgServiceActor
|
||||||
|
) => Promise<TAzureDevOpsConnection>;
|
||||||
|
|
||||||
|
type TAzureDevOpsProject = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
url?: string;
|
||||||
|
state?: string;
|
||||||
|
visibility?: string;
|
||||||
|
lastUpdateTime?: string;
|
||||||
|
revision?: number;
|
||||||
|
abbreviation?: string;
|
||||||
|
defaultTeamImageUrl?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TAzureDevOpsProjectsResponse = {
|
||||||
|
count: number;
|
||||||
|
value: TAzureDevOpsProject[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAuthHeaders = (appConnection: TAzureDevOpsConnection, accessToken: string) => {
|
||||||
|
switch (appConnection.method) {
|
||||||
|
case AzureDevOpsConnectionMethod.OAuth:
|
||||||
|
return {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
Accept: "application/json"
|
||||||
|
};
|
||||||
|
case AzureDevOpsConnectionMethod.AccessToken:
|
||||||
|
// For access token, create Basic auth header
|
||||||
|
const basicAuthToken = Buffer.from(`user:${accessToken}`).toString("base64");
|
||||||
|
return {
|
||||||
|
Authorization: `Basic ${basicAuthToken}`,
|
||||||
|
Accept: "application/json"
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new BadRequestError({ message: "Unsupported connection method" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const listAzureDevOpsProjects = async (
|
||||||
|
appConnection: TAzureDevOpsConnection,
|
||||||
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">,
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||||
|
): Promise<TAzureDevOpsProject[]> => {
|
||||||
|
const accessToken = await getAzureDevopsConnection(appConnection.id, appConnectionDAL, kmsService);
|
||||||
|
|
||||||
|
// Both OAuth and access Token methods use organization name from credentials
|
||||||
|
const credentials = appConnection.credentials as { orgName: string };
|
||||||
|
const { orgName } = credentials;
|
||||||
|
|
||||||
|
// Use the standard Azure DevOps Projects API endpoint
|
||||||
|
// This endpoint returns only projects that the authenticated user has access to
|
||||||
|
const devOpsEndpoint = `${IntegrationUrls.AZURE_DEVOPS_API_URL}/${encodeURIComponent(orgName)}/_apis/projects?api-version=7.1`;
|
||||||
|
try {
|
||||||
|
const { data } = await request.get<TAzureDevOpsProjectsResponse>(devOpsEndpoint, {
|
||||||
|
headers: getAuthHeaders(appConnection, accessToken)
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.value || [];
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
// Provide more specific error messages based on the response
|
||||||
|
if (error?.response?.status === 401) {
|
||||||
|
throw new Error(
|
||||||
|
`Authentication failed for Azure DevOps organization: ${orgName}. Please check your credentials and ensure the token has the required scopes (vso.project or vso.profile).`
|
||||||
|
);
|
||||||
|
} else if (error?.response?.status === 403) {
|
||||||
|
throw new Error(
|
||||||
|
`Access denied to Azure DevOps organization: ${orgName}. Please ensure the user has access to the organization.`
|
||||||
|
);
|
||||||
|
} else if (error?.response?.status === 404) {
|
||||||
|
throw new Error(`Azure DevOps organization not found: ${orgName}. Please verify the organization name.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const azureDevOpsConnectionService = (
|
||||||
|
getAppConnection: TGetAppConnectionFunc,
|
||||||
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">,
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||||
|
) => {
|
||||||
|
const listProjects = async (connectionId: string, actor: OrgServiceActor) => {
|
||||||
|
const appConnection = await getAppConnection(AppConnection.AzureDevOps, connectionId, actor);
|
||||||
|
|
||||||
|
const projects = await listAzureDevOpsProjects(appConnection, appConnectionDAL, kmsService);
|
||||||
|
|
||||||
|
return projects.map((project) => ({
|
||||||
|
id: project.id,
|
||||||
|
name: project.name,
|
||||||
|
appId: project.id,
|
||||||
|
description: project.description,
|
||||||
|
url: project.url,
|
||||||
|
state: project.state,
|
||||||
|
visibility: project.visibility,
|
||||||
|
lastUpdateTime: project.lastUpdateTime,
|
||||||
|
revision: project.revision,
|
||||||
|
abbreviation: project.abbreviation,
|
||||||
|
defaultTeamImageUrl: project.defaultTeamImageUrl
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
listProjects
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,54 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { DiscriminativePick } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import {
|
||||||
|
AzureDevOpsConnectionOAuthOutputCredentialsSchema,
|
||||||
|
AzureDevOpsConnectionSchema,
|
||||||
|
CreateAzureDevOpsConnectionSchema,
|
||||||
|
ValidateAzureDevOpsConnectionCredentialsSchema
|
||||||
|
} from "./azure-devops-schemas";
|
||||||
|
|
||||||
|
export type TAzureDevOpsConnection = z.infer<typeof AzureDevOpsConnectionSchema>;
|
||||||
|
|
||||||
|
export type TAzureDevOpsConnectionInput = z.infer<typeof CreateAzureDevOpsConnectionSchema> & {
|
||||||
|
app: AppConnection.AzureDevOps;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TValidateAzureDevOpsConnectionCredentialsSchema = typeof ValidateAzureDevOpsConnectionCredentialsSchema;
|
||||||
|
|
||||||
|
export type TAzureDevOpsConnectionConfig = DiscriminativePick<
|
||||||
|
TAzureDevOpsConnectionInput,
|
||||||
|
"method" | "app" | "credentials"
|
||||||
|
> & {
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TAzureDevOpsConnectionCredentials = z.infer<typeof AzureDevOpsConnectionOAuthOutputCredentialsSchema>;
|
||||||
|
|
||||||
|
export interface ExchangeCodeAzureResponse {
|
||||||
|
token_type: string;
|
||||||
|
scope: string;
|
||||||
|
expires_in: number;
|
||||||
|
ext_expires_in: number;
|
||||||
|
access_token: string;
|
||||||
|
refresh_token: string;
|
||||||
|
id_token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TAzureRegisteredApp {
|
||||||
|
id: string;
|
||||||
|
appId: string;
|
||||||
|
displayName: string;
|
||||||
|
description?: string;
|
||||||
|
createdDateTime: string;
|
||||||
|
identifierUris?: string[];
|
||||||
|
signInAudience?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TAzureListRegisteredAppsResponse {
|
||||||
|
"@odata.context": string;
|
||||||
|
"@odata.nextLink"?: string;
|
||||||
|
value: TAzureRegisteredApp[];
|
||||||
|
}
|
@@ -16,7 +16,8 @@ const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
|
|||||||
const SQL_CONNECTION_CLIENT_MAP = {
|
const SQL_CONNECTION_CLIENT_MAP = {
|
||||||
[AppConnection.Postgres]: "pg",
|
[AppConnection.Postgres]: "pg",
|
||||||
[AppConnection.MsSql]: "mssql",
|
[AppConnection.MsSql]: "mssql",
|
||||||
[AppConnection.MySql]: "mysql2"
|
[AppConnection.MySql]: "mysql2",
|
||||||
|
[AppConnection.OracleDB]: "oracledb"
|
||||||
};
|
};
|
||||||
|
|
||||||
const getConnectionConfig = ({
|
const getConnectionConfig = ({
|
||||||
@@ -57,6 +58,17 @@ const getConnectionConfig = ({
|
|||||||
: false
|
: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case AppConnection.OracleDB: {
|
||||||
|
return {
|
||||||
|
ssl: sslEnabled
|
||||||
|
? {
|
||||||
|
sslCA: sslCertificate,
|
||||||
|
sslServerDNMatch: sslRejectUnauthorized
|
||||||
|
}
|
||||||
|
: false
|
||||||
|
};
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled SQL Connection Config: ${app as AppConnection}`);
|
throw new Error(`Unhandled SQL Connection Config: ${app as AppConnection}`);
|
||||||
}
|
}
|
||||||
@@ -114,7 +126,8 @@ export const SQL_CONNECTION_ALTER_LOGIN_STATEMENT: Record<
|
|||||||
> = {
|
> = {
|
||||||
[AppConnection.Postgres]: ({ username, password }) => [`ALTER USER ?? WITH PASSWORD '${password}';`, [username]],
|
[AppConnection.Postgres]: ({ username, password }) => [`ALTER USER ?? WITH PASSWORD '${password}';`, [username]],
|
||||||
[AppConnection.MsSql]: ({ username, password }) => [`ALTER LOGIN ?? WITH PASSWORD = '${password}';`, [username]],
|
[AppConnection.MsSql]: ({ username, password }) => [`ALTER LOGIN ?? WITH PASSWORD = '${password}';`, [username]],
|
||||||
[AppConnection.MySql]: ({ username, password }) => [`ALTER USER ??@'%' IDENTIFIED BY '${password}';`, [username]]
|
[AppConnection.MySql]: ({ username, password }) => [`ALTER USER ??@'%' IDENTIFIED BY '${password}';`, [username]],
|
||||||
|
[AppConnection.OracleDB]: ({ username, password }) => [`ALTER USER ?? IDENTIFIED BY "${password}"`, [username]]
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transferSqlConnectionCredentialsToPlatform = async (
|
export const transferSqlConnectionCredentialsToPlatform = async (
|
||||||
|
@@ -120,7 +120,7 @@ export const folderCommitChangesDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
return docs.map((doc) => {
|
return docs.map((doc) => {
|
||||||
// Determine if this is a secret or folder change based on populated fields
|
// Determine if this is a secret or folder change based on populated fields
|
||||||
if (doc.secretKey && doc.secretVersion && doc.secretId) {
|
if (doc.secretKey && doc.secretVersion !== null && doc.secretId) {
|
||||||
return {
|
return {
|
||||||
...doc,
|
...doc,
|
||||||
resourceType: "secret",
|
resourceType: "secret",
|
||||||
@@ -168,7 +168,7 @@ export const folderCommitChangesDALFactory = (db: TDbClient) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return docs
|
return docs
|
||||||
.filter((doc) => doc.secretKey && doc.secretVersion && doc.secretId)
|
.filter((doc) => doc.secretKey && doc.secretVersion !== null && doc.secretId)
|
||||||
.map(
|
.map(
|
||||||
(doc): SecretCommitChange => ({
|
(doc): SecretCommitChange => ({
|
||||||
...doc,
|
...doc,
|
||||||
@@ -209,7 +209,7 @@ export const folderCommitChangesDALFactory = (db: TDbClient) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return docs
|
return docs
|
||||||
.filter((doc) => doc.folderName && doc.folderVersion && doc.folderChangeId)
|
.filter((doc) => doc.folderName && doc.folderVersion !== null && doc.folderChangeId)
|
||||||
.map(
|
.map(
|
||||||
(doc): FolderCommitChange => ({
|
(doc): FolderCommitChange => ({
|
||||||
...doc,
|
...doc,
|
||||||
|
@@ -815,7 +815,7 @@ export const folderCommitServiceFactory = ({
|
|||||||
encryptedComment: version1.encryptedComment
|
encryptedComment: version1.encryptedComment
|
||||||
? secretManagerDecryptor({ cipherTextBlob: version1.encryptedComment }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: version1.encryptedComment }).toString()
|
||||||
: "",
|
: "",
|
||||||
metadata: version1.metadata as { key: string; value: string }[],
|
metadata: Array.isArray(version1.metadata) ? (version1.metadata as { key: string; value: string }[]) : [],
|
||||||
tags: version1.tags.map((tag) => tag.id)
|
tags: version1.tags.map((tag) => tag.id)
|
||||||
};
|
};
|
||||||
const version2Reshaped = {
|
const version2Reshaped = {
|
||||||
@@ -826,7 +826,7 @@ export const folderCommitServiceFactory = ({
|
|||||||
encryptedComment: version2.encryptedComment
|
encryptedComment: version2.encryptedComment
|
||||||
? secretManagerDecryptor({ cipherTextBlob: version2.encryptedComment }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: version2.encryptedComment }).toString()
|
||||||
: "",
|
: "",
|
||||||
metadata: version2.metadata as { key: string; value: string }[],
|
metadata: Array.isArray(version2.metadata) ? (version2.metadata as { key: string; value: string }[]) : [],
|
||||||
tags: version2.tags.map((tag) => tag.id)
|
tags: version2.tags.map((tag) => tag.id)
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
@@ -28,6 +28,11 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.IdentityUniversalAuth}.id`
|
`${TableName.IdentityUniversalAuth}.id`
|
||||||
)
|
)
|
||||||
.leftJoin(TableName.IdentityGcpAuth, `${TableName.Identity}.id`, `${TableName.IdentityGcpAuth}.identityId`)
|
.leftJoin(TableName.IdentityGcpAuth, `${TableName.Identity}.id`, `${TableName.IdentityGcpAuth}.identityId`)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.IdentityAliCloudAuth,
|
||||||
|
`${TableName.Identity}.id`,
|
||||||
|
`${TableName.IdentityAliCloudAuth}.identityId`
|
||||||
|
)
|
||||||
.leftJoin(TableName.IdentityAwsAuth, `${TableName.Identity}.id`, `${TableName.IdentityAwsAuth}.identityId`)
|
.leftJoin(TableName.IdentityAwsAuth, `${TableName.Identity}.id`, `${TableName.IdentityAwsAuth}.identityId`)
|
||||||
.leftJoin(TableName.IdentityAzureAuth, `${TableName.Identity}.id`, `${TableName.IdentityAzureAuth}.identityId`)
|
.leftJoin(TableName.IdentityAzureAuth, `${TableName.Identity}.id`, `${TableName.IdentityAzureAuth}.identityId`)
|
||||||
.leftJoin(TableName.IdentityLdapAuth, `${TableName.Identity}.id`, `${TableName.IdentityLdapAuth}.identityId`)
|
.leftJoin(TableName.IdentityLdapAuth, `${TableName.Identity}.id`, `${TableName.IdentityLdapAuth}.identityId`)
|
||||||
@@ -44,6 +49,10 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
|||||||
.select(
|
.select(
|
||||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityUniversalAuth).as("accessTokenTrustedIpsUa"),
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityUniversalAuth).as("accessTokenTrustedIpsUa"),
|
||||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityGcpAuth).as("accessTokenTrustedIpsGcp"),
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityGcpAuth).as("accessTokenTrustedIpsGcp"),
|
||||||
|
db
|
||||||
|
.ref("accessTokenTrustedIps")
|
||||||
|
.withSchema(TableName.IdentityAliCloudAuth)
|
||||||
|
.as("accessTokenTrustedIpsAliCloud"),
|
||||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityAwsAuth).as("accessTokenTrustedIpsAws"),
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityAwsAuth).as("accessTokenTrustedIpsAws"),
|
||||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityAzureAuth).as("accessTokenTrustedIpsAzure"),
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityAzureAuth).as("accessTokenTrustedIpsAzure"),
|
||||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityKubernetesAuth).as("accessTokenTrustedIpsK8s"),
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityKubernetesAuth).as("accessTokenTrustedIpsK8s"),
|
||||||
@@ -62,6 +71,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
|||||||
...doc,
|
...doc,
|
||||||
trustedIpsUniversalAuth: doc.accessTokenTrustedIpsUa,
|
trustedIpsUniversalAuth: doc.accessTokenTrustedIpsUa,
|
||||||
trustedIpsGcpAuth: doc.accessTokenTrustedIpsGcp,
|
trustedIpsGcpAuth: doc.accessTokenTrustedIpsGcp,
|
||||||
|
trustedIpsAliCloudAuth: doc.accessTokenTrustedIpsAliCloud,
|
||||||
trustedIpsAwsAuth: doc.accessTokenTrustedIpsAws,
|
trustedIpsAwsAuth: doc.accessTokenTrustedIpsAws,
|
||||||
trustedIpsAzureAuth: doc.accessTokenTrustedIpsAzure,
|
trustedIpsAzureAuth: doc.accessTokenTrustedIpsAzure,
|
||||||
trustedIpsKubernetesAuth: doc.accessTokenTrustedIpsK8s,
|
trustedIpsKubernetesAuth: doc.accessTokenTrustedIpsK8s,
|
||||||
|
@@ -193,6 +193,7 @@ export const identityAccessTokenServiceFactory = ({
|
|||||||
const trustedIpsMap: Record<IdentityAuthMethod, unknown> = {
|
const trustedIpsMap: Record<IdentityAuthMethod, unknown> = {
|
||||||
[IdentityAuthMethod.UNIVERSAL_AUTH]: identityAccessToken.trustedIpsUniversalAuth,
|
[IdentityAuthMethod.UNIVERSAL_AUTH]: identityAccessToken.trustedIpsUniversalAuth,
|
||||||
[IdentityAuthMethod.GCP_AUTH]: identityAccessToken.trustedIpsGcpAuth,
|
[IdentityAuthMethod.GCP_AUTH]: identityAccessToken.trustedIpsGcpAuth,
|
||||||
|
[IdentityAuthMethod.ALICLOUD_AUTH]: identityAccessToken.trustedIpsAliCloudAuth,
|
||||||
[IdentityAuthMethod.AWS_AUTH]: identityAccessToken.trustedIpsAwsAuth,
|
[IdentityAuthMethod.AWS_AUTH]: identityAccessToken.trustedIpsAwsAuth,
|
||||||
[IdentityAuthMethod.OCI_AUTH]: identityAccessToken.trustedIpsOciAuth,
|
[IdentityAuthMethod.OCI_AUTH]: identityAccessToken.trustedIpsOciAuth,
|
||||||
[IdentityAuthMethod.AZURE_AUTH]: identityAccessToken.trustedIpsAzureAuth,
|
[IdentityAuthMethod.AZURE_AUTH]: identityAccessToken.trustedIpsAzureAuth,
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TIdentityAliCloudAuthDALFactory = ReturnType<typeof identityAliCloudAuthDALFactory>;
|
||||||
|
|
||||||
|
export const identityAliCloudAuthDALFactory = (db: TDbClient) => {
|
||||||
|
return ormify(db, TableName.IdentityAliCloudAuth);
|
||||||
|
};
|
@@ -0,0 +1,361 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
|
import { IdentityAuthMethod } from "@app/db/schemas";
|
||||||
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
|
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
|
import {
|
||||||
|
constructPermissionErrorMessage,
|
||||||
|
validatePrivilegeChangeOperation
|
||||||
|
} from "@app/ee/services/permission/permission-fns";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
|
||||||
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
|
||||||
|
import { ActorType, AuthTokenType } from "../auth/auth-type";
|
||||||
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
|
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
||||||
|
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
||||||
|
import { validateIdentityUpdateForSuperAdminPrivileges } from "../super-admin/super-admin-fns";
|
||||||
|
import { TIdentityAliCloudAuthDALFactory } from "./identity-alicloud-auth-dal";
|
||||||
|
import {
|
||||||
|
TAliCloudGetUserResponse,
|
||||||
|
TAttachAliCloudAuthDTO,
|
||||||
|
TGetAliCloudAuthDTO,
|
||||||
|
TLoginAliCloudAuthDTO,
|
||||||
|
TRevokeAliCloudAuthDTO,
|
||||||
|
TUpdateAliCloudAuthDTO
|
||||||
|
} from "./identity-alicloud-auth-types";
|
||||||
|
|
||||||
|
type TIdentityAliCloudAuthServiceFactoryDep = {
|
||||||
|
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
|
||||||
|
identityAliCloudAuthDAL: Pick<
|
||||||
|
TIdentityAliCloudAuthDALFactory,
|
||||||
|
"findOne" | "transaction" | "create" | "updateById" | "delete"
|
||||||
|
>;
|
||||||
|
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIdentityAliCloudAuthServiceFactory = ReturnType<typeof identityAliCloudAuthServiceFactory>;
|
||||||
|
|
||||||
|
export const identityAliCloudAuthServiceFactory = ({
|
||||||
|
identityAccessTokenDAL,
|
||||||
|
identityAliCloudAuthDAL,
|
||||||
|
identityOrgMembershipDAL,
|
||||||
|
licenseService,
|
||||||
|
permissionService
|
||||||
|
}: TIdentityAliCloudAuthServiceFactoryDep) => {
|
||||||
|
const login = async ({ identityId, ...params }: TLoginAliCloudAuthDTO) => {
|
||||||
|
const identityAliCloudAuth = await identityAliCloudAuthDAL.findOne({ identityId });
|
||||||
|
if (!identityAliCloudAuth) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Alibaba Cloud auth method not found for identity, did you configure Alibaba Cloud auth?"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({
|
||||||
|
identityId: identityAliCloudAuth.identityId
|
||||||
|
});
|
||||||
|
|
||||||
|
const requestUrl = new URL("https://sts.aliyuncs.com");
|
||||||
|
|
||||||
|
for (const key of Object.keys(params)) {
|
||||||
|
requestUrl.searchParams.set(key, (params as Record<string, string>)[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await request.get<TAliCloudGetUserResponse>(requestUrl.toString()).catch((err: AxiosError) => {
|
||||||
|
logger.error(err.response, "AliCloudIdentityLogin: Failed to authenticate with Alibaba Cloud");
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (identityAliCloudAuth.allowedArns) {
|
||||||
|
// In the future we could do partial checks for role ARNs
|
||||||
|
const isAccountAllowed = identityAliCloudAuth.allowedArns.split(",").some((arn) => arn.trim() === data.Arn);
|
||||||
|
|
||||||
|
if (!isAccountAllowed)
|
||||||
|
throw new UnauthorizedError({
|
||||||
|
message: "Access denied: Alibaba Cloud account ARN not allowed."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the token
|
||||||
|
const identityAccessToken = await identityAliCloudAuthDAL.transaction(async (tx) => {
|
||||||
|
const newToken = await identityAccessTokenDAL.create(
|
||||||
|
{
|
||||||
|
identityId: identityAliCloudAuth.identityId,
|
||||||
|
isAccessTokenRevoked: false,
|
||||||
|
accessTokenTTL: identityAliCloudAuth.accessTokenTTL,
|
||||||
|
accessTokenMaxTTL: identityAliCloudAuth.accessTokenMaxTTL,
|
||||||
|
accessTokenNumUses: 0,
|
||||||
|
accessTokenNumUsesLimit: identityAliCloudAuth.accessTokenNumUsesLimit,
|
||||||
|
authMethod: IdentityAuthMethod.ALICLOUD_AUTH
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
return newToken;
|
||||||
|
});
|
||||||
|
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const accessToken = jwt.sign(
|
||||||
|
{
|
||||||
|
identityId: identityAliCloudAuth.identityId,
|
||||||
|
identityAccessTokenId: identityAccessToken.id,
|
||||||
|
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
|
||||||
|
} as TIdentityAccessTokenJwtPayload,
|
||||||
|
appCfg.AUTH_SECRET,
|
||||||
|
Number(identityAccessToken.accessTokenTTL) === 0
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
expiresIn: Number(identityAccessToken.accessTokenTTL)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
identityAliCloudAuth,
|
||||||
|
accessToken,
|
||||||
|
identityAccessToken,
|
||||||
|
identityMembershipOrg
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const attachAliCloudAuth = async ({
|
||||||
|
identityId,
|
||||||
|
allowedArns,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
isActorSuperAdmin
|
||||||
|
}: TAttachAliCloudAuthDTO) => {
|
||||||
|
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
||||||
|
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
|
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to add Alibaba Cloud Auth to already configured identity"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) {
|
||||||
|
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
|
||||||
|
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
|
||||||
|
if (
|
||||||
|
!plan.ipAllowlisting &&
|
||||||
|
accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" &&
|
||||||
|
accessTokenTrustedIp.ipAddress !== "::/0"
|
||||||
|
)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to add IP access range to access token due to plan restriction. Upgrade plan to add IP access range."
|
||||||
|
});
|
||||||
|
if (!isValidIpOrCidr(accessTokenTrustedIp.ipAddress))
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
||||||
|
});
|
||||||
|
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
||||||
|
});
|
||||||
|
|
||||||
|
const identityAliCloudAuth = await identityAliCloudAuthDAL.transaction(async (tx) => {
|
||||||
|
const doc = await identityAliCloudAuthDAL.create(
|
||||||
|
{
|
||||||
|
identityId: identityMembershipOrg.identityId,
|
||||||
|
type: "iam",
|
||||||
|
allowedArns,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps)
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
return doc;
|
||||||
|
});
|
||||||
|
return { ...identityAliCloudAuth, orgId: identityMembershipOrg.orgId };
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateAliCloudAuth = async ({
|
||||||
|
identityId,
|
||||||
|
allowedArns,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TUpdateAliCloudAuthDTO) => {
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
|
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "The identity does not have Alibaba Cloud Auth attached"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const identityAliCloudAuth = await identityAliCloudAuthDAL.findOne({ identityId });
|
||||||
|
|
||||||
|
if (
|
||||||
|
(accessTokenMaxTTL || identityAliCloudAuth.accessTokenMaxTTL) > 0 &&
|
||||||
|
(accessTokenTTL || identityAliCloudAuth.accessTokenTTL) >
|
||||||
|
(accessTokenMaxTTL || identityAliCloudAuth.accessTokenMaxTTL)
|
||||||
|
) {
|
||||||
|
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
|
||||||
|
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
|
||||||
|
if (
|
||||||
|
!plan.ipAllowlisting &&
|
||||||
|
accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" &&
|
||||||
|
accessTokenTrustedIp.ipAddress !== "::/0"
|
||||||
|
)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to add IP access range to access token due to plan restriction. Upgrade plan to add IP access range."
|
||||||
|
});
|
||||||
|
if (!isValidIpOrCidr(accessTokenTrustedIp.ipAddress))
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
||||||
|
});
|
||||||
|
return extractIPDetails(accessTokenTrustedIp.ipAddress);
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedAliCloudAuth = await identityAliCloudAuthDAL.updateById(identityAliCloudAuth.id, {
|
||||||
|
allowedArns,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenTrustedIps: reformattedAccessTokenTrustedIps
|
||||||
|
? JSON.stringify(reformattedAccessTokenTrustedIps)
|
||||||
|
: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ...updatedAliCloudAuth, orgId: identityMembershipOrg.orgId };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAliCloudAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetAliCloudAuthDTO) => {
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
|
||||||
|
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The identity does not have Alibaba Cloud Auth attached"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const alicloudIdentityAuth = await identityAliCloudAuthDAL.findOne({ identityId });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||||
|
return { ...alicloudIdentityAuth, orgId: identityMembershipOrg.orgId };
|
||||||
|
};
|
||||||
|
|
||||||
|
const revokeIdentityAliCloudAuth = async ({
|
||||||
|
identityId,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TRevokeAliCloudAuthDTO) => {
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
|
||||||
|
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
|
||||||
|
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The identity does not have Alibaba Cloud auth"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { permission, membership } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
const { permission: rolePermission } = await permissionService.getOrgPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityMembershipOrg.identityId,
|
||||||
|
identityMembershipOrg.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||||
|
membership.shouldUseNewPrivilegeSystem,
|
||||||
|
OrgPermissionIdentityActions.RevokeAuth,
|
||||||
|
OrgPermissionSubjects.Identity,
|
||||||
|
permission,
|
||||||
|
rolePermission
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!permissionBoundary.isValid)
|
||||||
|
throw new PermissionBoundaryError({
|
||||||
|
message: constructPermissionErrorMessage(
|
||||||
|
"Failed to revoke Alibaba Cloud auth of identity with more privileged role",
|
||||||
|
membership.shouldUseNewPrivilegeSystem,
|
||||||
|
OrgPermissionIdentityActions.RevokeAuth,
|
||||||
|
OrgPermissionSubjects.Identity
|
||||||
|
),
|
||||||
|
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||||
|
});
|
||||||
|
|
||||||
|
const revokedIdentityAliCloudAuth = await identityAliCloudAuthDAL.transaction(async (tx) => {
|
||||||
|
const deletedAliCloudAuth = await identityAliCloudAuthDAL.delete({ identityId }, tx);
|
||||||
|
await identityAccessTokenDAL.delete({ identityId, authMethod: IdentityAuthMethod.ALICLOUD_AUTH }, tx);
|
||||||
|
|
||||||
|
return { ...deletedAliCloudAuth?.[0], orgId: identityMembershipOrg.orgId };
|
||||||
|
});
|
||||||
|
return revokedIdentityAliCloudAuth;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
login,
|
||||||
|
attachAliCloudAuth,
|
||||||
|
updateAliCloudAuth,
|
||||||
|
getAliCloudAuth,
|
||||||
|
revokeIdentityAliCloudAuth
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,45 @@
|
|||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export type TLoginAliCloudAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
Action: string;
|
||||||
|
Format: string;
|
||||||
|
Version: string;
|
||||||
|
AccessKeyId: string;
|
||||||
|
SignatureMethod: string;
|
||||||
|
Timestamp: string;
|
||||||
|
SignatureVersion: string;
|
||||||
|
SignatureNonce: string;
|
||||||
|
Signature: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TAttachAliCloudAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
allowedArns: string;
|
||||||
|
accessTokenTTL: number;
|
||||||
|
accessTokenMaxTTL: number;
|
||||||
|
accessTokenNumUsesLimit: number;
|
||||||
|
accessTokenTrustedIps: { ipAddress: string }[];
|
||||||
|
isActorSuperAdmin?: boolean;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TUpdateAliCloudAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
allowedArns: string;
|
||||||
|
accessTokenTTL?: number;
|
||||||
|
accessTokenMaxTTL?: number;
|
||||||
|
accessTokenNumUsesLimit?: number;
|
||||||
|
accessTokenTrustedIps?: { ipAddress: string }[];
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TGetAliCloudAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TRevokeAliCloudAuthDTO = {
|
||||||
|
identityId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TAliCloudGetUserResponse = {
|
||||||
|
Arn: string;
|
||||||
|
};
|
@@ -0,0 +1,25 @@
|
|||||||
|
import RE2 from "re2";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const arnSchema = z
|
||||||
|
.string()
|
||||||
|
.refine(
|
||||||
|
(val) => new RE2("^acs:ram::[0-9]{16}:(user|role)/.*$").test(val),
|
||||||
|
"Invalid ARN format. Expected format: acs:ram::[0-9]{16}:(user|role)/*"
|
||||||
|
);
|
||||||
|
export const validateArns = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "Allowed ARNs required")
|
||||||
|
.max(500, "Input exceeds the maximum limit of 500 characters")
|
||||||
|
.transform((val) => {
|
||||||
|
if (!val) return [];
|
||||||
|
return val
|
||||||
|
.split(",")
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
})
|
||||||
|
.refine((arr) => arr.every((name) => arnSchema.safeParse(name).success), {
|
||||||
|
message: "One or more ARNs are invalid"
|
||||||
|
})
|
||||||
|
.transform((arr) => arr.join(", "));
|
@@ -94,7 +94,9 @@ export const identityAwsAuthServiceFactory = ({
|
|||||||
|
|
||||||
const headers: TAwsGetCallerIdentityHeaders = JSON.parse(Buffer.from(iamRequestHeaders, "base64").toString());
|
const headers: TAwsGetCallerIdentityHeaders = JSON.parse(Buffer.from(iamRequestHeaders, "base64").toString());
|
||||||
const body: string = Buffer.from(iamRequestBody, "base64").toString();
|
const body: string = Buffer.from(iamRequestBody, "base64").toString();
|
||||||
const region = headers.Authorization ? awsRegionFromHeader(headers.Authorization) : null;
|
|
||||||
|
const authHeader = headers.Authorization || headers.authorization;
|
||||||
|
const region = authHeader ? awsRegionFromHeader(authHeader) : null;
|
||||||
|
|
||||||
if (!isValidAwsRegion(region)) {
|
if (!isValidAwsRegion(region)) {
|
||||||
throw new BadRequestError({ message: "Invalid AWS region" });
|
throw new BadRequestError({ message: "Invalid AWS region" });
|
||||||
|
@@ -40,7 +40,8 @@ export type TAwsGetCallerIdentityHeaders = {
|
|||||||
"X-Amz-Date": string;
|
"X-Amz-Date": string;
|
||||||
"Content-Length": number;
|
"Content-Length": number;
|
||||||
"x-amz-security-token": string;
|
"x-amz-security-token": string;
|
||||||
Authorization: string;
|
Authorization?: string;
|
||||||
|
authorization?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGetCallerIdentityResponse = {
|
export type TGetCallerIdentityResponse = {
|
||||||
|
@@ -72,8 +72,8 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
const $gatewayProxyWrapper = async <T>(
|
const $gatewayProxyWrapper = async <T>(
|
||||||
inputs: {
|
inputs: {
|
||||||
gatewayId: string;
|
gatewayId: string;
|
||||||
targetHost: string;
|
targetHost?: string;
|
||||||
targetPort: number;
|
targetPort?: number;
|
||||||
caCert?: string;
|
caCert?: string;
|
||||||
reviewTokenThroughGateway: boolean;
|
reviewTokenThroughGateway: boolean;
|
||||||
},
|
},
|
||||||
@@ -104,12 +104,16 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
cert: relayDetails.certificate,
|
cert: relayDetails.certificate,
|
||||||
key: relayDetails.privateKey.toString()
|
key: relayDetails.privateKey.toString()
|
||||||
},
|
},
|
||||||
// we always pass this, because its needed for both tcp and http protocol
|
// only needed for TCP protocol, because the gateway as reviewer will use the pod's CA cert for auth directly
|
||||||
|
...(!inputs.reviewTokenThroughGateway
|
||||||
|
? {
|
||||||
httpsAgent: new https.Agent({
|
httpsAgent: new https.Agent({
|
||||||
ca: inputs.caCert,
|
ca: inputs.caCert,
|
||||||
rejectUnauthorized: Boolean(inputs.caCert)
|
rejectUnauthorized: Boolean(inputs.caCert)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
: {})
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return callbackResult;
|
return callbackResult;
|
||||||
@@ -142,8 +146,15 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
caCert = decryptor({ cipherTextBlob: identityKubernetesAuth.encryptedKubernetesCaCertificate }).toString();
|
caCert = decryptor({ cipherTextBlob: identityKubernetesAuth.encryptedKubernetesCaCertificate }).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenReviewCallbackRaw = async (host: string = identityKubernetesAuth.kubernetesHost, port?: number) => {
|
const tokenReviewCallbackRaw = async (host = identityKubernetesAuth.kubernetesHost, port?: number) => {
|
||||||
logger.info({ host, port }, "tokenReviewCallbackRaw: Processing kubernetes token review using raw API");
|
logger.info({ host, port }, "tokenReviewCallbackRaw: Processing kubernetes token review using raw API");
|
||||||
|
|
||||||
|
if (!host || !identityKubernetesAuth.kubernetesHost) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Kubernetes host is required when token review mode is set to API"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let tokenReviewerJwt = "";
|
let tokenReviewerJwt = "";
|
||||||
if (identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt) {
|
if (identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt) {
|
||||||
tokenReviewerJwt = decryptor({
|
tokenReviewerJwt = decryptor({
|
||||||
@@ -211,11 +222,7 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
return res.data;
|
return res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const tokenReviewCallbackThroughGateway = async (
|
const tokenReviewCallbackThroughGateway = async (host: string, port?: number) => {
|
||||||
host: string = identityKubernetesAuth.kubernetesHost,
|
|
||||||
port?: number,
|
|
||||||
httpsAgent?: https.Agent
|
|
||||||
) => {
|
|
||||||
logger.info(
|
logger.info(
|
||||||
{
|
{
|
||||||
host,
|
host,
|
||||||
@@ -224,11 +231,9 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
"tokenReviewCallbackThroughGateway: Processing kubernetes token review using gateway"
|
"tokenReviewCallbackThroughGateway: Processing kubernetes token review using gateway"
|
||||||
);
|
);
|
||||||
|
|
||||||
const baseUrl = port ? `${host}:${port}` : host;
|
|
||||||
|
|
||||||
const res = await axios
|
const res = await axios
|
||||||
.post<TCreateTokenReviewResponse>(
|
.post<TCreateTokenReviewResponse>(
|
||||||
`${baseUrl}/apis/authentication.k8s.io/v1/tokenreviews`,
|
`${host}:${port}/apis/authentication.k8s.io/v1/tokenreviews`,
|
||||||
{
|
{
|
||||||
apiVersion: "authentication.k8s.io/v1",
|
apiVersion: "authentication.k8s.io/v1",
|
||||||
kind: "TokenReview",
|
kind: "TokenReview",
|
||||||
@@ -240,11 +245,10 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken
|
"x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount
|
||||||
},
|
},
|
||||||
signal: AbortSignal.timeout(10000),
|
signal: AbortSignal.timeout(10000),
|
||||||
timeout: 10000,
|
timeout: 10000
|
||||||
...(httpsAgent ? { httpsAgent } : {})
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@@ -273,29 +277,6 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
let data: TCreateTokenReviewResponse | undefined;
|
let data: TCreateTokenReviewResponse | undefined;
|
||||||
|
|
||||||
if (identityKubernetesAuth.tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Gateway) {
|
if (identityKubernetesAuth.tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Gateway) {
|
||||||
const { kubernetesHost } = identityKubernetesAuth;
|
|
||||||
|
|
||||||
let urlString = kubernetesHost;
|
|
||||||
if (!kubernetesHost.startsWith("http://") && !kubernetesHost.startsWith("https://")) {
|
|
||||||
urlString = `https://${kubernetesHost}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = new URL(urlString);
|
|
||||||
let { port: k8sPort } = url;
|
|
||||||
const { protocol, hostname: k8sHost } = url;
|
|
||||||
|
|
||||||
const cleanedProtocol = new RE2(/[^a-zA-Z0-9]/g).replace(protocol, "").toLowerCase();
|
|
||||||
|
|
||||||
if (!["https", "http"].includes(cleanedProtocol)) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Invalid Kubernetes host URL, must start with http:// or https://"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!k8sPort) {
|
|
||||||
k8sPort = cleanedProtocol === "https" ? "443" : "80";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!identityKubernetesAuth.gatewayId) {
|
if (!identityKubernetesAuth.gatewayId) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Gateway ID is required when token review mode is set to Gateway"
|
message: "Gateway ID is required when token review mode is set to Gateway"
|
||||||
@@ -305,14 +286,17 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
data = await $gatewayProxyWrapper(
|
data = await $gatewayProxyWrapper(
|
||||||
{
|
{
|
||||||
gatewayId: identityKubernetesAuth.gatewayId,
|
gatewayId: identityKubernetesAuth.gatewayId,
|
||||||
targetHost: `${cleanedProtocol}://${k8sHost}`, // note(daniel): must include the protocol (https|http)
|
|
||||||
targetPort: k8sPort ? Number(k8sPort) : 443,
|
|
||||||
caCert,
|
|
||||||
reviewTokenThroughGateway: true
|
reviewTokenThroughGateway: true
|
||||||
},
|
},
|
||||||
tokenReviewCallbackThroughGateway
|
tokenReviewCallbackThroughGateway
|
||||||
);
|
);
|
||||||
} else if (identityKubernetesAuth.tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Api) {
|
} else if (identityKubernetesAuth.tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Api) {
|
||||||
|
if (!identityKubernetesAuth.kubernetesHost) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Kubernetes host is required when token review mode is set to API"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let { kubernetesHost } = identityKubernetesAuth;
|
let { kubernetesHost } = identityKubernetesAuth;
|
||||||
if (kubernetesHost.startsWith("https://") || kubernetesHost.startsWith("http://")) {
|
if (kubernetesHost.startsWith("https://") || kubernetesHost.startsWith("http://")) {
|
||||||
kubernetesHost = new RE2("^https?:\\/\\/").replace(kubernetesHost, "");
|
kubernetesHost = new RE2("^https?:\\/\\/").replace(kubernetesHost, "");
|
||||||
|
@@ -12,7 +12,7 @@ export enum IdentityKubernetesAuthTokenReviewMode {
|
|||||||
|
|
||||||
export type TAttachKubernetesAuthDTO = {
|
export type TAttachKubernetesAuthDTO = {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
kubernetesHost: string;
|
kubernetesHost: string | null;
|
||||||
caCert: string;
|
caCert: string;
|
||||||
tokenReviewerJwt?: string;
|
tokenReviewerJwt?: string;
|
||||||
tokenReviewMode: IdentityKubernetesAuthTokenReviewMode;
|
tokenReviewMode: IdentityKubernetesAuthTokenReviewMode;
|
||||||
@@ -29,7 +29,7 @@ export type TAttachKubernetesAuthDTO = {
|
|||||||
|
|
||||||
export type TUpdateKubernetesAuthDTO = {
|
export type TUpdateKubernetesAuthDTO = {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
kubernetesHost?: string;
|
kubernetesHost?: string | null;
|
||||||
caCert?: string;
|
caCert?: string;
|
||||||
tokenReviewerJwt?: string | null;
|
tokenReviewerJwt?: string | null;
|
||||||
tokenReviewMode?: IdentityKubernetesAuthTokenReviewMode;
|
tokenReviewMode?: IdentityKubernetesAuthTokenReviewMode;
|
||||||
|
@@ -4,6 +4,7 @@ import { TDbClient } from "@app/db";
|
|||||||
import {
|
import {
|
||||||
TableName,
|
TableName,
|
||||||
TIdentities,
|
TIdentities,
|
||||||
|
TIdentityAlicloudAuths,
|
||||||
TIdentityAwsAuths,
|
TIdentityAwsAuths,
|
||||||
TIdentityAzureAuths,
|
TIdentityAzureAuths,
|
||||||
TIdentityGcpAuths,
|
TIdentityGcpAuths,
|
||||||
@@ -57,6 +58,11 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.IdentityProjectMembership}.identityId`,
|
`${TableName.IdentityProjectMembership}.identityId`,
|
||||||
`${TableName.IdentityGcpAuth}.identityId`
|
`${TableName.IdentityGcpAuth}.identityId`
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.IdentityAliCloudAuth,
|
||||||
|
`${TableName.IdentityProjectMembership}.identityId`,
|
||||||
|
`${TableName.IdentityAliCloudAuth}.identityId`
|
||||||
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.IdentityAwsAuth,
|
TableName.IdentityAwsAuth,
|
||||||
`${TableName.IdentityProjectMembership}.identityId`,
|
`${TableName.IdentityProjectMembership}.identityId`,
|
||||||
@@ -111,6 +117,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("type").as("projectType").withSchema(TableName.Project),
|
db.ref("type").as("projectType").withSchema(TableName.Project),
|
||||||
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
||||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||||
|
db.ref("id").as("alicloudId").withSchema(TableName.IdentityAliCloudAuth),
|
||||||
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||||
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||||
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||||
@@ -267,6 +274,11 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.Identity}.id`,
|
`${TableName.Identity}.id`,
|
||||||
`${TableName.IdentityGcpAuth}.identityId`
|
`${TableName.IdentityGcpAuth}.identityId`
|
||||||
)
|
)
|
||||||
|
.leftJoin<TIdentityAlicloudAuths>(
|
||||||
|
TableName.IdentityAliCloudAuth,
|
||||||
|
`${TableName.Identity}.id`,
|
||||||
|
`${TableName.IdentityAliCloudAuth}.identityId`
|
||||||
|
)
|
||||||
.leftJoin<TIdentityAwsAuths>(
|
.leftJoin<TIdentityAwsAuths>(
|
||||||
TableName.IdentityAwsAuth,
|
TableName.IdentityAwsAuth,
|
||||||
`${TableName.Identity}.id`,
|
`${TableName.Identity}.id`,
|
||||||
@@ -319,6 +331,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("name").as("projectName").withSchema(TableName.Project),
|
db.ref("name").as("projectName").withSchema(TableName.Project),
|
||||||
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
||||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||||
|
db.ref("id").as("alicloudId").withSchema(TableName.IdentityAliCloudAuth),
|
||||||
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||||
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||||
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||||
@@ -346,6 +359,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
identityId,
|
identityId,
|
||||||
identityName,
|
identityName,
|
||||||
uaId,
|
uaId,
|
||||||
|
alicloudId,
|
||||||
awsId,
|
awsId,
|
||||||
gcpId,
|
gcpId,
|
||||||
kubernetesId,
|
kubernetesId,
|
||||||
@@ -367,6 +381,7 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
name: identityName,
|
name: identityName,
|
||||||
authMethods: buildAuthMethods({
|
authMethods: buildAuthMethods({
|
||||||
uaId,
|
uaId,
|
||||||
|
alicloudId,
|
||||||
awsId,
|
awsId,
|
||||||
gcpId,
|
gcpId,
|
||||||
kubernetesId,
|
kubernetesId,
|
||||||
|
@@ -122,9 +122,9 @@ export const identityUaServiceFactory = ({
|
|||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
accessTokenTTL: identityUa.accessTokenPeriod,
|
accessTokenTTL: identityUa.accessTokenPeriod,
|
||||||
// Setting Max TTL to 2 × period ensures that clients can always renew their token
|
// We set a very large Max TTL for periodic tokens to ensure that clients (even outdated ones) can always renew their token
|
||||||
// at least once, and matches client logic that checks if renewing would exceed Max TTL.
|
// without them having to update their SDKs, CLIs, etc. This workaround sets it to 30 years to emulate "forever"
|
||||||
accessTokenMaxTTL: 2 * identityUa.accessTokenPeriod
|
accessTokenMaxTTL: 1000000000
|
||||||
};
|
};
|
||||||
|
|
||||||
const identityAccessToken = await identityUaDAL.transaction(async (tx) => {
|
const identityAccessToken = await identityUaDAL.transaction(async (tx) => {
|
||||||
|
@@ -3,6 +3,7 @@ import { IdentityAuthMethod } from "@app/db/schemas";
|
|||||||
export const buildAuthMethods = ({
|
export const buildAuthMethods = ({
|
||||||
uaId,
|
uaId,
|
||||||
gcpId,
|
gcpId,
|
||||||
|
alicloudId,
|
||||||
awsId,
|
awsId,
|
||||||
kubernetesId,
|
kubernetesId,
|
||||||
ociId,
|
ociId,
|
||||||
@@ -14,6 +15,7 @@ export const buildAuthMethods = ({
|
|||||||
}: {
|
}: {
|
||||||
uaId?: string;
|
uaId?: string;
|
||||||
gcpId?: string;
|
gcpId?: string;
|
||||||
|
alicloudId?: string;
|
||||||
awsId?: string;
|
awsId?: string;
|
||||||
kubernetesId?: string;
|
kubernetesId?: string;
|
||||||
ociId?: string;
|
ociId?: string;
|
||||||
@@ -26,6 +28,7 @@ export const buildAuthMethods = ({
|
|||||||
return [
|
return [
|
||||||
...[uaId ? IdentityAuthMethod.UNIVERSAL_AUTH : null],
|
...[uaId ? IdentityAuthMethod.UNIVERSAL_AUTH : null],
|
||||||
...[gcpId ? IdentityAuthMethod.GCP_AUTH : null],
|
...[gcpId ? IdentityAuthMethod.GCP_AUTH : null],
|
||||||
|
...[alicloudId ? IdentityAuthMethod.ALICLOUD_AUTH : null],
|
||||||
...[awsId ? IdentityAuthMethod.AWS_AUTH : null],
|
...[awsId ? IdentityAuthMethod.AWS_AUTH : null],
|
||||||
...[kubernetesId ? IdentityAuthMethod.KUBERNETES_AUTH : null],
|
...[kubernetesId ? IdentityAuthMethod.KUBERNETES_AUTH : null],
|
||||||
...[ociId ? IdentityAuthMethod.OCI_AUTH : null],
|
...[ociId ? IdentityAuthMethod.OCI_AUTH : null],
|
||||||
|
@@ -3,6 +3,7 @@ import { Knex } from "knex";
|
|||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import {
|
import {
|
||||||
TableName,
|
TableName,
|
||||||
|
TIdentityAlicloudAuths,
|
||||||
TIdentityAwsAuths,
|
TIdentityAwsAuths,
|
||||||
TIdentityAzureAuths,
|
TIdentityAzureAuths,
|
||||||
TIdentityGcpAuths,
|
TIdentityGcpAuths,
|
||||||
@@ -53,6 +54,11 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.IdentityOrgMembership}.identityId`,
|
`${TableName.IdentityOrgMembership}.identityId`,
|
||||||
`${TableName.IdentityGcpAuth}.identityId`
|
`${TableName.IdentityGcpAuth}.identityId`
|
||||||
)
|
)
|
||||||
|
.leftJoin<TIdentityAlicloudAuths>(
|
||||||
|
TableName.IdentityAliCloudAuth,
|
||||||
|
`${TableName.IdentityOrgMembership}.identityId`,
|
||||||
|
`${TableName.IdentityAliCloudAuth}.identityId`
|
||||||
|
)
|
||||||
.leftJoin<TIdentityAwsAuths>(
|
.leftJoin<TIdentityAwsAuths>(
|
||||||
TableName.IdentityAwsAuth,
|
TableName.IdentityAwsAuth,
|
||||||
`${TableName.IdentityOrgMembership}.identityId`,
|
`${TableName.IdentityOrgMembership}.identityId`,
|
||||||
@@ -99,6 +105,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
||||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||||
|
db.ref("id").as("alicloudId").withSchema(TableName.IdentityAliCloudAuth),
|
||||||
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||||
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||||
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||||
@@ -183,6 +190,11 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
"paginatedIdentity.identityId",
|
"paginatedIdentity.identityId",
|
||||||
`${TableName.IdentityGcpAuth}.identityId`
|
`${TableName.IdentityGcpAuth}.identityId`
|
||||||
)
|
)
|
||||||
|
.leftJoin<TIdentityAlicloudAuths>(
|
||||||
|
TableName.IdentityAliCloudAuth,
|
||||||
|
"paginatedIdentity.identityId",
|
||||||
|
`${TableName.IdentityAliCloudAuth}.identityId`
|
||||||
|
)
|
||||||
.leftJoin<TIdentityAwsAuths>(
|
.leftJoin<TIdentityAwsAuths>(
|
||||||
TableName.IdentityAwsAuth,
|
TableName.IdentityAwsAuth,
|
||||||
"paginatedIdentity.identityId",
|
"paginatedIdentity.identityId",
|
||||||
@@ -236,6 +248,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
||||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||||
|
db.ref("id").as("alicloudId").withSchema(TableName.IdentityAliCloudAuth),
|
||||||
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||||
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||||
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||||
@@ -278,6 +291,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
id,
|
id,
|
||||||
orgId,
|
orgId,
|
||||||
uaId,
|
uaId,
|
||||||
|
alicloudId,
|
||||||
awsId,
|
awsId,
|
||||||
gcpId,
|
gcpId,
|
||||||
jwtId,
|
jwtId,
|
||||||
@@ -312,6 +326,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
name: identityName,
|
name: identityName,
|
||||||
authMethods: buildAuthMethods({
|
authMethods: buildAuthMethods({
|
||||||
uaId,
|
uaId,
|
||||||
|
alicloudId,
|
||||||
awsId,
|
awsId,
|
||||||
gcpId,
|
gcpId,
|
||||||
kubernetesId,
|
kubernetesId,
|
||||||
@@ -459,6 +474,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
||||||
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||||
|
db.ref("id").as("alicloudId").withSchema(TableName.IdentityAliCloudAuth),
|
||||||
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||||
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||||
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||||
@@ -502,6 +518,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
total_count,
|
total_count,
|
||||||
id,
|
id,
|
||||||
uaId,
|
uaId,
|
||||||
|
alicloudId,
|
||||||
awsId,
|
awsId,
|
||||||
gcpId,
|
gcpId,
|
||||||
jwtId,
|
jwtId,
|
||||||
@@ -536,6 +553,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
name: identityName,
|
name: identityName,
|
||||||
authMethods: buildAuthMethods({
|
authMethods: buildAuthMethods({
|
||||||
uaId,
|
uaId,
|
||||||
|
alicloudId,
|
||||||
awsId,
|
awsId,
|
||||||
gcpId,
|
gcpId,
|
||||||
kubernetesId,
|
kubernetesId,
|
||||||
|
@@ -1211,8 +1211,8 @@ export const orgServiceFactory = ({
|
|||||||
subjectLine: "Infisical organization invitation",
|
subjectLine: "Infisical organization invitation",
|
||||||
recipients: [el.email],
|
recipients: [el.email],
|
||||||
substitutions: {
|
substitutions: {
|
||||||
inviterFirstName: invitingUser.firstName,
|
inviterFirstName: invitingUser?.firstName,
|
||||||
inviterUsername: invitingUser.email,
|
inviterUsername: invitingUser?.email,
|
||||||
organizationName: org?.name,
|
organizationName: org?.name,
|
||||||
email: el.email,
|
email: el.email,
|
||||||
organizationId: org?.id.toString(),
|
organizationId: org?.id.toString(),
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
|
export const AZURE_DEVOPS_SYNC_LIST_OPTION: TSecretSyncListItem = {
|
||||||
|
name: "Azure DevOps",
|
||||||
|
destination: SecretSync.AzureDevOps,
|
||||||
|
connection: AppConnection.AzureDevOps,
|
||||||
|
canImportSecrets: false
|
||||||
|
};
|
@@ -0,0 +1,233 @@
|
|||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||||
|
import { AzureDevOpsConnectionMethod } from "@app/services/app-connection/azure-devops/azure-devops-enums";
|
||||||
|
import { getAzureDevopsConnection } from "@app/services/app-connection/azure-devops/azure-devops-fns";
|
||||||
|
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
|
import { TAzureDevOpsSyncWithCredentials } from "./azure-devops-sync-types";
|
||||||
|
|
||||||
|
type TAzureDevOpsSyncFactoryDeps = {
|
||||||
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "updateById">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface AzureDevOpsVariableGroup {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
type: string;
|
||||||
|
variables: Record<string, { value: string; isSecret: boolean }>;
|
||||||
|
variableGroupProjectReferences: Array<{
|
||||||
|
description: string;
|
||||||
|
name: string;
|
||||||
|
projectReference: { id: string; name: string };
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AzureDevOpsVariableGroupList {
|
||||||
|
count: number;
|
||||||
|
value: AzureDevOpsVariableGroup[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const azureDevOpsSyncFactory = ({ kmsService, appConnectionDAL }: TAzureDevOpsSyncFactoryDeps) => {
|
||||||
|
const getConnectionAuth = async (secretSync: TAzureDevOpsSyncWithCredentials) => {
|
||||||
|
const { credentials } = secretSync.connection;
|
||||||
|
const isOAuth = secretSync.connection.method === AzureDevOpsConnectionMethod.OAuth;
|
||||||
|
|
||||||
|
const { orgName } = credentials;
|
||||||
|
if (!orgName) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Azure DevOps: organization name is required"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessToken = await getAzureDevopsConnection(secretSync.connectionId, appConnectionDAL, kmsService);
|
||||||
|
|
||||||
|
return { accessToken, orgName, isOAuth };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAuthHeader = (accessToken: string, isOAuth: boolean) => {
|
||||||
|
if (isOAuth) {
|
||||||
|
return `Bearer ${accessToken}`;
|
||||||
|
}
|
||||||
|
const basicAuth = Buffer.from(`:${accessToken}`).toString("base64");
|
||||||
|
return `Basic ${basicAuth}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const $getEnvGroupId = async (
|
||||||
|
accessToken: string,
|
||||||
|
orgName: string,
|
||||||
|
projectId: string,
|
||||||
|
environmentName: string,
|
||||||
|
isOAuth: boolean
|
||||||
|
) => {
|
||||||
|
const url = `${IntegrationUrls.AZURE_DEVOPS_API_URL}/${encodeURIComponent(orgName)}/${encodeURIComponent(projectId)}/_apis/distributedtask/variablegroups?api-version=7.1`;
|
||||||
|
const response = await request.get<AzureDevOpsVariableGroupList>(url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: getAuthHeader(accessToken, isOAuth)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const group of response.data.value) {
|
||||||
|
if (group.name === environmentName) {
|
||||||
|
return { groupId: group.id.toString(), groupName: group.name };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { groupId: "", groupName: "" };
|
||||||
|
};
|
||||||
|
|
||||||
|
const syncSecrets = async (secretSync: TAzureDevOpsSyncWithCredentials, secretMap: TSecretMap) => {
|
||||||
|
if (!secretSync.destinationConfig.devopsProjectId) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Azure DevOps: project ID is required"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!secretSync.environment?.name) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Azure DevOps: environment name is required"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { accessToken, orgName, isOAuth } = await getConnectionAuth(secretSync);
|
||||||
|
|
||||||
|
const { groupId, groupName } = await $getEnvGroupId(
|
||||||
|
accessToken,
|
||||||
|
orgName,
|
||||||
|
secretSync.destinationConfig.devopsProjectId,
|
||||||
|
secretSync.environment.name,
|
||||||
|
isOAuth
|
||||||
|
);
|
||||||
|
|
||||||
|
const variables: Record<string, { value: string; isSecret: boolean }> = {};
|
||||||
|
for (const [key, secret] of Object.entries(secretMap)) {
|
||||||
|
if (secret?.value !== undefined) {
|
||||||
|
variables[key] = { value: secret.value, isSecret: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!groupId) {
|
||||||
|
// Create new variable group - API endpoint is organization-level
|
||||||
|
const url = `${IntegrationUrls.AZURE_DEVOPS_API_URL}/${encodeURIComponent(orgName)}/_apis/distributedtask/variablegroups?api-version=7.1`;
|
||||||
|
|
||||||
|
await request.post(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
name: secretSync.environment.name,
|
||||||
|
description: secretSync.environment.name,
|
||||||
|
type: "Vsts",
|
||||||
|
variables,
|
||||||
|
variableGroupProjectReferences: [
|
||||||
|
{
|
||||||
|
description: secretSync.environment.name,
|
||||||
|
name: secretSync.environment.name,
|
||||||
|
projectReference: {
|
||||||
|
id: secretSync.destinationConfig.devopsProjectId,
|
||||||
|
name: secretSync.destinationConfig.devopsProjectId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: getAuthHeader(accessToken, isOAuth),
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const url = `${IntegrationUrls.AZURE_DEVOPS_API_URL}/${encodeURIComponent(orgName)}/_apis/distributedtask/variablegroups/${groupId}?api-version=7.1`;
|
||||||
|
|
||||||
|
await request.put(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
name: groupName,
|
||||||
|
description: groupName,
|
||||||
|
type: "Vsts",
|
||||||
|
variables,
|
||||||
|
variableGroupProjectReferences: [
|
||||||
|
{
|
||||||
|
description: groupName,
|
||||||
|
name: groupName,
|
||||||
|
projectReference: {
|
||||||
|
id: secretSync.destinationConfig.devopsProjectId,
|
||||||
|
name: secretSync.destinationConfig.devopsProjectId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: getAuthHeader(accessToken, isOAuth),
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeSecrets = async (secretSync: TAzureDevOpsSyncWithCredentials) => {
|
||||||
|
const { accessToken, orgName, isOAuth } = await getConnectionAuth(secretSync);
|
||||||
|
|
||||||
|
const { groupId } = await $getEnvGroupId(
|
||||||
|
accessToken,
|
||||||
|
orgName,
|
||||||
|
secretSync.destinationConfig.devopsProjectId,
|
||||||
|
secretSync.environment?.name || "",
|
||||||
|
isOAuth
|
||||||
|
);
|
||||||
|
|
||||||
|
if (groupId) {
|
||||||
|
// Delete the variable group entirely using the DELETE API
|
||||||
|
const deleteUrl = `${IntegrationUrls.AZURE_DEVOPS_API_URL}/${encodeURIComponent(orgName)}/_apis/distributedtask/variablegroups/${groupId}?projectIds=${secretSync.destinationConfig.devopsProjectId}&api-version=7.1`;
|
||||||
|
|
||||||
|
await request.delete(deleteUrl, {
|
||||||
|
headers: {
|
||||||
|
Authorization: getAuthHeader(accessToken, isOAuth)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSecrets = async (secretSync: TAzureDevOpsSyncWithCredentials) => {
|
||||||
|
const { accessToken, orgName, isOAuth } = await getConnectionAuth(secretSync);
|
||||||
|
|
||||||
|
const { groupId } = await $getEnvGroupId(
|
||||||
|
accessToken,
|
||||||
|
orgName,
|
||||||
|
secretSync.destinationConfig.devopsProjectId,
|
||||||
|
secretSync.environment?.name || "",
|
||||||
|
isOAuth
|
||||||
|
);
|
||||||
|
|
||||||
|
const secretMap: TSecretMap = {};
|
||||||
|
|
||||||
|
if (groupId) {
|
||||||
|
const url = `${IntegrationUrls.AZURE_DEVOPS_API_URL}/${orgName}/_apis/distributedtask/variablegroups/${groupId}?api-version=7.1`;
|
||||||
|
const response = await request.get<AzureDevOpsVariableGroup>(url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: getAuthHeader(accessToken, isOAuth)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response?.data?.variables) {
|
||||||
|
Object.entries(response.data.variables).forEach(([key, variable]) => {
|
||||||
|
secretMap[key] = {
|
||||||
|
value: variable.value || ""
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return secretMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
syncSecrets,
|
||||||
|
removeSecrets,
|
||||||
|
getSecrets
|
||||||
|
};
|
||||||
|
};
|
@@ -0,0 +1,50 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { SecretSyncs } from "@app/lib/api-docs";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
import {
|
||||||
|
BaseSecretSyncSchema,
|
||||||
|
GenericCreateSecretSyncFieldsSchema,
|
||||||
|
GenericUpdateSecretSyncFieldsSchema
|
||||||
|
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||||
|
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
|
export const AzureDevOpsSyncDestinationConfigSchema = z.object({
|
||||||
|
devopsProjectId: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Project ID required")
|
||||||
|
.describe(SecretSyncs.DESTINATION_CONFIG.AZURE_DEVOPS?.devopsProjectId || "Azure DevOps Project ID"),
|
||||||
|
devopsProjectName: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Project name required")
|
||||||
|
.describe(SecretSyncs.DESTINATION_CONFIG.AZURE_DEVOPS?.devopsProjectName || "Azure DevOps Project Name")
|
||||||
|
});
|
||||||
|
|
||||||
|
const AzureDevOpsSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
||||||
|
|
||||||
|
export const AzureDevOpsSyncSchema = BaseSecretSyncSchema(SecretSync.AzureDevOps, AzureDevOpsSyncOptionsConfig).extend({
|
||||||
|
destination: z.literal(SecretSync.AzureDevOps),
|
||||||
|
destinationConfig: AzureDevOpsSyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateAzureDevOpsSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.AzureDevOps,
|
||||||
|
AzureDevOpsSyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destinationConfig: AzureDevOpsSyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateAzureDevOpsSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.AzureDevOps,
|
||||||
|
AzureDevOpsSyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destinationConfig: AzureDevOpsSyncDestinationConfigSchema.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureDevOpsSyncListItemSchema = z.object({
|
||||||
|
name: z.literal("Azure DevOps"),
|
||||||
|
connection: z.literal(AppConnection.AzureDevOps),
|
||||||
|
destination: z.literal(SecretSync.AzureDevOps),
|
||||||
|
canImportSecrets: z.literal(false)
|
||||||
|
});
|
@@ -0,0 +1,22 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TAzureDevOpsConnection } from "@app/services/app-connection/azure-devops/azure-devops-types";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AzureDevOpsSyncDestinationConfigSchema,
|
||||||
|
AzureDevOpsSyncListItemSchema,
|
||||||
|
AzureDevOpsSyncSchema,
|
||||||
|
CreateAzureDevOpsSyncSchema
|
||||||
|
} from "./azure-devops-sync-schemas";
|
||||||
|
|
||||||
|
export type TAzureDevOpsSync = z.infer<typeof AzureDevOpsSyncSchema>;
|
||||||
|
|
||||||
|
export type TAzureDevOpsSyncInput = z.infer<typeof CreateAzureDevOpsSyncSchema>;
|
||||||
|
|
||||||
|
export type TAzureDevOpsSyncListItem = z.infer<typeof AzureDevOpsSyncListItemSchema>;
|
||||||
|
|
||||||
|
export type TAzureDevOpsSyncDestinationConfig = z.infer<typeof AzureDevOpsSyncDestinationConfigSchema>;
|
||||||
|
|
||||||
|
export type TAzureDevOpsSyncWithCredentials = TAzureDevOpsSync & {
|
||||||
|
connection: TAzureDevOpsConnection;
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user