Compare commits

..

38 Commits

Author SHA1 Message Date
1da4cf85f8 rename schema file 2024-05-09 11:59:47 -04:00
42471b22bb Finish AWS Auth mermaid diagram 2024-05-08 21:52:56 -07:00
15ea96815c Rename AWS IAM auth to AWS Auth with IAM type 2024-05-08 21:22:23 -07:00
f74993e850 Merge pull request #1803 from Infisical/misc/improved-select-path-component-ux-1
misc: added handling of input focus to select path component
2024-05-08 22:00:02 +08:00
d0036a5656 Merge remote-tracking branch 'origin/main' into misc/improved-select-path-component-ux-1 2024-05-08 17:28:31 +08:00
e7f19421ef misc: resolved auto-popup of suggestions 2024-05-08 17:24:06 +08:00
e18d830fe8 Merge pull request #1801 from Infisical/daniel/k8-recursive
Feat: Recursive support for K8 operaetor
2024-05-08 00:44:07 +02:00
be2fc4fec4 Update Chart.yaml 2024-05-08 00:42:38 +02:00
829dbb9970 Update values.yaml 2024-05-08 00:41:53 +02:00
0b012c5dfb Chore: Helm 2024-05-08 00:23:50 +02:00
b0421ccad0 Docs: Add recursive to example 2024-05-08 00:21:08 +02:00
6b83326d00 Feat: Recursive mode support 2024-05-08 00:18:53 +02:00
1f6abc7f27 Feat: Recursive mode and fix error formatting 2024-05-08 00:18:40 +02:00
4a02520147 Update sample 2024-05-08 00:18:26 +02:00
14f38eb961 Feat: Recursive mode types 2024-05-08 00:16:51 +02:00
37a59b2576 Merge pull request #1799 from Infisical/create-pull-request/patch-1715116016
GH Action: rename new migration file timestamp
2024-05-07 14:27:45 -07:00
cebd22da8e chore: renamed new migration files to latest timestamp (gh-action) 2024-05-07 21:06:55 +00:00
d200405c6e Merge pull request #1778 from Infisical/aws-iam-auth
AWS IAM Authentication Method
2024-05-07 14:06:30 -07:00
3a1cdc4f44 Delete backend/src/db/migrations/20240507162149_test.ts 2024-05-07 15:41:09 -04:00
2929d94f0a Merge pull request #1797 from Infisical/maidul98-patch-10
test
2024-05-07 14:28:03 -04:00
0383ae9e8b Create 20240507162149_test.ts 2024-05-07 14:27:44 -04:00
00faa6257f Delete backend/src/db/migrations/20240507162149_test.ts 2024-05-07 14:27:33 -04:00
183bde55ca correctly fetch merged by user login 2024-05-07 14:26:56 -04:00
c96fc1f798 Merge pull request #1795 from Infisical/maidul98-patch-9
test
2024-05-07 14:09:49 -04:00
80f7ff1ea8 Create 20240507162149_test.ts 2024-05-07 14:09:38 -04:00
c87620109b Rename 20240507162141_access to 20240507162141_access.ts 2024-05-07 13:58:10 -04:00
02c158b4ed Delete backend/src/db/migrations/20240507162180_test 2024-05-07 13:47:25 -04:00
588f4bdb09 Fix merge conflict 2024-05-07 10:45:07 -07:00
ddfa64eb33 Merge pull request #1793 from Infisical/maidul98-patch-8
testing-ignore
2024-05-07 13:27:19 -04:00
8bab14a672 misc: added handling of input focus 2024-05-08 00:43:14 +08:00
e3c80309c3 Move aws auth migration file to front 2024-05-06 23:03:45 -07:00
ec3d6c20e8 Merge remote-tracking branch 'origin' into aws-iam-auth 2024-05-06 22:58:47 -07:00
5d7c0f30c8 Fix typo universal auth 2024-05-06 22:58:35 -07:00
0b089e6fa6 Update aws iam auth fns filename 2024-05-06 18:35:34 -07:00
cbf8e041e9 Finish docs for AWS IAM Auth, update ARN regex 2024-05-03 17:20:44 -07:00
5c4d35e30a Merge remote-tracking branch 'origin' into aws-iam-auth 2024-05-02 22:53:14 -07:00
d5c74d558a Start docs for AWS IAM auth 2024-05-02 22:52:37 -07:00
9c002ad645 Finish preliminary AWS IAM Auth method 2024-05-02 22:42:02 -07:00
52 changed files with 2344 additions and 411 deletions

View File

@ -38,13 +38,15 @@ jobs:
rm added_files.txt
git commit -m "chore: renamed new migration files to latest timestamp (gh-action)"
- name: Get the username of the person who closed the PR
run: |
- name: Get PR details
id: pr_details
run: |
PR_NUMBER=${{ github.event.pull_request.number }}
PR_CLOSER=$(curl -s -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUMBER" | jq -r '.closed_by.login')
PR_MERGER=$(curl -s "https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUMBER" | jq -r '.merged_by.login')
echo "PR Number: $PR_NUMBER"
echo "PR Closer: $PR_CLOSER"
echo "pr_closer=$PR_CLOSER" >> $GITHUB_OUTPUT
echo "PR Merger: $PR_MERGER"
echo "pr_merger=$PR_MERGER" >> $GITHUB_OUTPUT
- name: Create Pull Request
if: env.SKIP_RENAME != 'true'
@ -54,3 +56,4 @@ jobs:
commit-message: 'chore: renamed new migration files to latest UTC (gh-action)'
title: 'GH Action: rename new migration file timestamp'
branch-suffix: timestamp
reviewers: ${{ steps.pr_details.outputs.pr_merger }}

View File

@ -1207,6 +1207,58 @@
"node": ">=14.0.0"
}
},
"node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/client-sts": {
"version": "3.504.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.504.0.tgz",
"integrity": "sha512-IESs8FkL7B/uY+ml4wgoRkrr6xYo4PizcNw6JX17eveq1gRBCPKeGMjE6HTDOcIYZZ8rqz/UeuH3JD4UhrMOnA==",
"dependencies": {
"@aws-crypto/sha256-browser": "3.0.0",
"@aws-crypto/sha256-js": "3.0.0",
"@aws-sdk/core": "3.496.0",
"@aws-sdk/middleware-host-header": "3.502.0",
"@aws-sdk/middleware-logger": "3.502.0",
"@aws-sdk/middleware-recursion-detection": "3.502.0",
"@aws-sdk/middleware-user-agent": "3.502.0",
"@aws-sdk/region-config-resolver": "3.502.0",
"@aws-sdk/types": "3.502.0",
"@aws-sdk/util-endpoints": "3.502.0",
"@aws-sdk/util-user-agent-browser": "3.502.0",
"@aws-sdk/util-user-agent-node": "3.502.0",
"@smithy/config-resolver": "^2.1.1",
"@smithy/core": "^1.3.1",
"@smithy/fetch-http-handler": "^2.4.1",
"@smithy/hash-node": "^2.1.1",
"@smithy/invalid-dependency": "^2.1.1",
"@smithy/middleware-content-length": "^2.1.1",
"@smithy/middleware-endpoint": "^2.4.1",
"@smithy/middleware-retry": "^2.1.1",
"@smithy/middleware-serde": "^2.1.1",
"@smithy/middleware-stack": "^2.1.1",
"@smithy/node-config-provider": "^2.2.1",
"@smithy/node-http-handler": "^2.3.1",
"@smithy/protocol-http": "^3.1.1",
"@smithy/smithy-client": "^2.3.1",
"@smithy/types": "^2.9.1",
"@smithy/url-parser": "^2.1.1",
"@smithy/util-base64": "^2.1.1",
"@smithy/util-body-length-browser": "^2.1.1",
"@smithy/util-body-length-node": "^2.2.1",
"@smithy/util-defaults-mode-browser": "^2.1.1",
"@smithy/util-defaults-mode-node": "^2.1.1",
"@smithy/util-endpoints": "^1.1.1",
"@smithy/util-middleware": "^2.1.1",
"@smithy/util-retry": "^2.1.1",
"@smithy/util-utf8": "^2.1.1",
"fast-xml-parser": "4.2.5",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"@aws-sdk/credential-provider-node": "^3.504.0"
}
},
"node_modules/@aws-sdk/client-secrets-manager/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
@ -1314,7 +1366,7 @@
"@aws-sdk/credential-provider-node": "^3.504.0"
}
},
"node_modules/@aws-sdk/client-sts": {
"node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/client-sts": {
"version": "3.504.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.504.0.tgz",
"integrity": "sha512-IESs8FkL7B/uY+ml4wgoRkrr6xYo4PizcNw6JX17eveq1gRBCPKeGMjE6HTDOcIYZZ8rqz/UeuH3JD4UhrMOnA==",
@ -1436,6 +1488,58 @@
"node": ">=14.0.0"
}
},
"node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/client-sts": {
"version": "3.504.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.504.0.tgz",
"integrity": "sha512-IESs8FkL7B/uY+ml4wgoRkrr6xYo4PizcNw6JX17eveq1gRBCPKeGMjE6HTDOcIYZZ8rqz/UeuH3JD4UhrMOnA==",
"dependencies": {
"@aws-crypto/sha256-browser": "3.0.0",
"@aws-crypto/sha256-js": "3.0.0",
"@aws-sdk/core": "3.496.0",
"@aws-sdk/middleware-host-header": "3.502.0",
"@aws-sdk/middleware-logger": "3.502.0",
"@aws-sdk/middleware-recursion-detection": "3.502.0",
"@aws-sdk/middleware-user-agent": "3.502.0",
"@aws-sdk/region-config-resolver": "3.502.0",
"@aws-sdk/types": "3.502.0",
"@aws-sdk/util-endpoints": "3.502.0",
"@aws-sdk/util-user-agent-browser": "3.502.0",
"@aws-sdk/util-user-agent-node": "3.502.0",
"@smithy/config-resolver": "^2.1.1",
"@smithy/core": "^1.3.1",
"@smithy/fetch-http-handler": "^2.4.1",
"@smithy/hash-node": "^2.1.1",
"@smithy/invalid-dependency": "^2.1.1",
"@smithy/middleware-content-length": "^2.1.1",
"@smithy/middleware-endpoint": "^2.4.1",
"@smithy/middleware-retry": "^2.1.1",
"@smithy/middleware-serde": "^2.1.1",
"@smithy/middleware-stack": "^2.1.1",
"@smithy/node-config-provider": "^2.2.1",
"@smithy/node-http-handler": "^2.3.1",
"@smithy/protocol-http": "^3.1.1",
"@smithy/smithy-client": "^2.3.1",
"@smithy/types": "^2.9.1",
"@smithy/url-parser": "^2.1.1",
"@smithy/util-base64": "^2.1.1",
"@smithy/util-body-length-browser": "^2.1.1",
"@smithy/util-body-length-node": "^2.2.1",
"@smithy/util-defaults-mode-browser": "^2.1.1",
"@smithy/util-defaults-mode-node": "^2.1.1",
"@smithy/util-endpoints": "^1.1.1",
"@smithy/util-middleware": "^2.1.1",
"@smithy/util-retry": "^2.1.1",
"@smithy/util-utf8": "^2.1.1",
"fast-xml-parser": "4.2.5",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"@aws-sdk/credential-provider-node": "^3.504.0"
}
},
"node_modules/@aws-sdk/credential-provider-node": {
"version": "3.504.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.504.0.tgz",
@ -1505,6 +1609,58 @@
"node": ">=14.0.0"
}
},
"node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/client-sts": {
"version": "3.504.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.504.0.tgz",
"integrity": "sha512-IESs8FkL7B/uY+ml4wgoRkrr6xYo4PizcNw6JX17eveq1gRBCPKeGMjE6HTDOcIYZZ8rqz/UeuH3JD4UhrMOnA==",
"dependencies": {
"@aws-crypto/sha256-browser": "3.0.0",
"@aws-crypto/sha256-js": "3.0.0",
"@aws-sdk/core": "3.496.0",
"@aws-sdk/middleware-host-header": "3.502.0",
"@aws-sdk/middleware-logger": "3.502.0",
"@aws-sdk/middleware-recursion-detection": "3.502.0",
"@aws-sdk/middleware-user-agent": "3.502.0",
"@aws-sdk/region-config-resolver": "3.502.0",
"@aws-sdk/types": "3.502.0",
"@aws-sdk/util-endpoints": "3.502.0",
"@aws-sdk/util-user-agent-browser": "3.502.0",
"@aws-sdk/util-user-agent-node": "3.502.0",
"@smithy/config-resolver": "^2.1.1",
"@smithy/core": "^1.3.1",
"@smithy/fetch-http-handler": "^2.4.1",
"@smithy/hash-node": "^2.1.1",
"@smithy/invalid-dependency": "^2.1.1",
"@smithy/middleware-content-length": "^2.1.1",
"@smithy/middleware-endpoint": "^2.4.1",
"@smithy/middleware-retry": "^2.1.1",
"@smithy/middleware-serde": "^2.1.1",
"@smithy/middleware-stack": "^2.1.1",
"@smithy/node-config-provider": "^2.2.1",
"@smithy/node-http-handler": "^2.3.1",
"@smithy/protocol-http": "^3.1.1",
"@smithy/smithy-client": "^2.3.1",
"@smithy/types": "^2.9.1",
"@smithy/url-parser": "^2.1.1",
"@smithy/util-base64": "^2.1.1",
"@smithy/util-body-length-browser": "^2.1.1",
"@smithy/util-body-length-node": "^2.2.1",
"@smithy/util-defaults-mode-browser": "^2.1.1",
"@smithy/util-defaults-mode-node": "^2.1.1",
"@smithy/util-endpoints": "^1.1.1",
"@smithy/util-middleware": "^2.1.1",
"@smithy/util-retry": "^2.1.1",
"@smithy/util-utf8": "^2.1.1",
"fast-xml-parser": "4.2.5",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"@aws-sdk/credential-provider-node": "^3.504.0"
}
},
"node_modules/@aws-sdk/middleware-host-header": {
"version": "3.502.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.502.0.tgz",
@ -3657,60 +3813,60 @@
}
},
"node_modules/@smithy/abort-controller": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.1.3.tgz",
"integrity": "sha512-c2aYH2Wu1RVE3rLlVgg2kQOBJGM0WbjReQi5DnPTm2Zb7F0gk7J2aeQeaX2u/lQZoHl6gv8Oac7mt9alU3+f4A==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.2.0.tgz",
"integrity": "sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw==",
"dependencies": {
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/config-resolver": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.1.4.tgz",
"integrity": "sha512-AW2WUZmBAzgO3V3ovKtsUbI3aBNMeQKFDumoqkNxaVDWF/xfnxAWqBKDr/NuG7c06N2Rm4xeZLPiJH/d+na0HA==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.2.0.tgz",
"integrity": "sha512-fsiMgd8toyUba6n1WRmr+qACzXltpdDkPTAaDqc8QqPBUzO+/JKwL6bUBseHVi8tu9l+3JOK+tSf7cay+4B3LA==",
"dependencies": {
"@smithy/node-config-provider": "^2.2.4",
"@smithy/types": "^2.10.1",
"@smithy/util-config-provider": "^2.2.1",
"@smithy/util-middleware": "^2.1.3",
"tslib": "^2.5.0"
"@smithy/node-config-provider": "^2.3.0",
"@smithy/types": "^2.12.0",
"@smithy/util-config-provider": "^2.3.0",
"@smithy/util-middleware": "^2.2.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/core": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-1.3.5.tgz",
"integrity": "sha512-Rrc+e2Jj6Gu7Xbn0jvrzZlSiP2CZocIOfZ9aNUA82+1sa6GBnxqL9+iZ9EKHeD9aqD1nU8EK4+oN2EiFpSv7Yw==",
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-1.4.2.tgz",
"integrity": "sha512-2fek3I0KZHWJlRLvRTqxTEri+qV0GRHrJIoLFuBMZB4EMg4WgeBGfF0X6abnrNYpq55KJ6R4D6x4f0vLnhzinA==",
"dependencies": {
"@smithy/middleware-endpoint": "^2.4.4",
"@smithy/middleware-retry": "^2.1.4",
"@smithy/middleware-serde": "^2.1.3",
"@smithy/protocol-http": "^3.2.1",
"@smithy/smithy-client": "^2.4.2",
"@smithy/types": "^2.10.1",
"@smithy/util-middleware": "^2.1.3",
"tslib": "^2.5.0"
"@smithy/middleware-endpoint": "^2.5.1",
"@smithy/middleware-retry": "^2.3.1",
"@smithy/middleware-serde": "^2.3.0",
"@smithy/protocol-http": "^3.3.0",
"@smithy/smithy-client": "^2.5.1",
"@smithy/types": "^2.12.0",
"@smithy/util-middleware": "^2.2.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/credential-provider-imds": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.2.4.tgz",
"integrity": "sha512-DdatjmBZQnhGe1FhI8gO98f7NmvQFSDiZTwC3WMvLTCKQUY+Y1SVkhJqIuLu50Eb7pTheoXQmK+hKYUgpUWsNA==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.3.0.tgz",
"integrity": "sha512-BWB9mIukO1wjEOo1Ojgl6LrG4avcaC7T/ZP6ptmAaW4xluhSIPZhY+/PI5YKzlk+jsm+4sQZB45Bt1OfMeQa3w==",
"dependencies": {
"@smithy/node-config-provider": "^2.2.4",
"@smithy/property-provider": "^2.1.3",
"@smithy/types": "^2.10.1",
"@smithy/url-parser": "^2.1.3",
"tslib": "^2.5.0"
"@smithy/node-config-provider": "^2.3.0",
"@smithy/property-provider": "^2.2.0",
"@smithy/types": "^2.12.0",
"@smithy/url-parser": "^2.2.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
@ -3779,459 +3935,451 @@
}
},
"node_modules/@smithy/fetch-http-handler": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.4.3.tgz",
"integrity": "sha512-Fn/KYJFo6L5I4YPG8WQb2hOmExgRmNpVH5IK2zU3JKrY5FKW7y9ar5e0BexiIC9DhSKqKX+HeWq/Y18fq7Dkpw==",
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.5.0.tgz",
"integrity": "sha512-BOWEBeppWhLn/no/JxUL/ghTfANTjT7kg3Ww2rPqTUY9R4yHPXxJ9JhMe3Z03LN3aPwiwlpDIUcVw1xDyHqEhw==",
"dependencies": {
"@smithy/protocol-http": "^3.2.1",
"@smithy/querystring-builder": "^2.1.3",
"@smithy/types": "^2.10.1",
"@smithy/util-base64": "^2.1.1",
"tslib": "^2.5.0"
"@smithy/protocol-http": "^3.3.0",
"@smithy/querystring-builder": "^2.2.0",
"@smithy/types": "^2.12.0",
"@smithy/util-base64": "^2.3.0",
"tslib": "^2.6.2"
}
},
"node_modules/@smithy/hash-node": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.1.3.tgz",
"integrity": "sha512-FsAPCUj7VNJIdHbSxMd5uiZiF20G2zdSDgrgrDrHqIs/VMxK85Vqk5kMVNNDMCZmMezp6UKnac0B4nAyx7HJ9g==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.2.0.tgz",
"integrity": "sha512-zLWaC/5aWpMrHKpoDF6nqpNtBhlAYKF/7+9yMN7GpdR8CzohnWfGtMznPybnwSS8saaXBMxIGwJqR4HmRp6b3g==",
"dependencies": {
"@smithy/types": "^2.10.1",
"@smithy/util-buffer-from": "^2.1.1",
"@smithy/util-utf8": "^2.1.1",
"tslib": "^2.5.0"
"@smithy/types": "^2.12.0",
"@smithy/util-buffer-from": "^2.2.0",
"@smithy/util-utf8": "^2.3.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/invalid-dependency": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.1.3.tgz",
"integrity": "sha512-wkra7d/G4CbngV4xsjYyAYOvdAhahQje/WymuQdVEnXFExJopEu7fbL5AEAlBPgWHXwu94VnCSG00gVzRfExyg==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.2.0.tgz",
"integrity": "sha512-nEDASdbKFKPXN2O6lOlTgrEEOO9NHIeO+HVvZnkqc8h5U9g3BIhWsvzFo+UcUbliMHvKNPD/zVxDrkP1Sbgp8Q==",
"dependencies": {
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
}
},
"node_modules/@smithy/is-array-buffer": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.1.1.tgz",
"integrity": "sha512-xozSQrcUinPpNPNPds4S7z/FakDTh1MZWtRP/2vQtYB/u3HYrX2UXuZs+VhaKBd6Vc7g2XPr2ZtwGBNDN6fNKQ==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
"integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
"dependencies": {
"tslib": "^2.5.0"
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/middleware-content-length": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.1.3.tgz",
"integrity": "sha512-aJduhkC+dcXxdnv5ZpM3uMmtGmVFKx412R1gbeykS5HXDmRU6oSsyy2SoHENCkfOGKAQOjVE2WVqDJibC0d21g==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.2.0.tgz",
"integrity": "sha512-5bl2LG1Ah/7E5cMSC+q+h3IpVHMeOkG0yLRyQT1p2aMJkSrZG7RlXHPuAgb7EyaFeidKEnnd/fNaLLaKlHGzDQ==",
"dependencies": {
"@smithy/protocol-http": "^3.2.1",
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
"@smithy/protocol-http": "^3.3.0",
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/middleware-endpoint": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.4.4.tgz",
"integrity": "sha512-4yjHyHK2Jul4JUDBo2sTsWY9UshYUnXeb/TAK/MTaPEb8XQvDmpwSFnfIRDU45RY1a6iC9LCnmJNg/yHyfxqkw==",
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.5.1.tgz",
"integrity": "sha512-1/8kFp6Fl4OsSIVTWHnNjLnTL8IqpIb/D3sTSczrKFnrE9VMNWxnrRKNvpUHOJ6zpGD5f62TPm7+17ilTJpiCQ==",
"dependencies": {
"@smithy/middleware-serde": "^2.1.3",
"@smithy/node-config-provider": "^2.2.4",
"@smithy/shared-ini-file-loader": "^2.3.4",
"@smithy/types": "^2.10.1",
"@smithy/url-parser": "^2.1.3",
"@smithy/util-middleware": "^2.1.3",
"tslib": "^2.5.0"
"@smithy/middleware-serde": "^2.3.0",
"@smithy/node-config-provider": "^2.3.0",
"@smithy/shared-ini-file-loader": "^2.4.0",
"@smithy/types": "^2.12.0",
"@smithy/url-parser": "^2.2.0",
"@smithy/util-middleware": "^2.2.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/middleware-retry": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.1.4.tgz",
"integrity": "sha512-Cyolv9YckZTPli1EkkaS39UklonxMd08VskiuMhURDjC0HHa/AD6aK/YoD21CHv9s0QLg0WMLvk9YeLTKkXaFQ==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.3.1.tgz",
"integrity": "sha512-P2bGufFpFdYcWvqpyqqmalRtwFUNUA8vHjJR5iGqbfR6mp65qKOLcUd6lTr4S9Gn/enynSrSf3p3FVgVAf6bXA==",
"dependencies": {
"@smithy/node-config-provider": "^2.2.4",
"@smithy/protocol-http": "^3.2.1",
"@smithy/service-error-classification": "^2.1.3",
"@smithy/smithy-client": "^2.4.2",
"@smithy/types": "^2.10.1",
"@smithy/util-middleware": "^2.1.3",
"@smithy/util-retry": "^2.1.3",
"tslib": "^2.5.0",
"uuid": "^8.3.2"
"@smithy/node-config-provider": "^2.3.0",
"@smithy/protocol-http": "^3.3.0",
"@smithy/service-error-classification": "^2.1.5",
"@smithy/smithy-client": "^2.5.1",
"@smithy/types": "^2.12.0",
"@smithy/util-middleware": "^2.2.0",
"@smithy/util-retry": "^2.2.0",
"tslib": "^2.6.2",
"uuid": "^9.0.1"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/middleware-retry/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/@smithy/middleware-serde": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.1.3.tgz",
"integrity": "sha512-s76LId+TwASrHhUa9QS4k/zeXDUAuNuddKklQzRgumbzge5BftVXHXIqL4wQxKGLocPwfgAOXWx+HdWhQk9hTg==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.3.0.tgz",
"integrity": "sha512-sIADe7ojwqTyvEQBe1nc/GXB9wdHhi9UwyX0lTyttmUWDJLP655ZYE1WngnNyXREme8I27KCaUhyhZWRXL0q7Q==",
"dependencies": {
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/middleware-stack": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.1.3.tgz",
"integrity": "sha512-opMFufVQgvBSld/b7mD7OOEBxF6STyraVr1xel1j0abVILM8ALJvRoFbqSWHGmaDlRGIiV9Q5cGbWi0sdiEaLQ==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.2.0.tgz",
"integrity": "sha512-Qntc3jrtwwrsAC+X8wms8zhrTr0sFXnyEGhZd9sLtsJ/6gGQKFzNB+wWbOcpJd7BR8ThNCoKt76BuQahfMvpeA==",
"dependencies": {
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/node-config-provider": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.2.4.tgz",
"integrity": "sha512-nqazHCp8r4KHSFhRQ+T0VEkeqvA0U+RhehBSr1gunUuNW3X7j0uDrWBxB2gE9eutzy6kE3Y7L+Dov/UXT871vg==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.3.0.tgz",
"integrity": "sha512-0elK5/03a1JPWMDPaS726Iw6LpQg80gFut1tNpPfxFuChEEklo2yL823V94SpTZTxmKlXFtFgsP55uh3dErnIg==",
"dependencies": {
"@smithy/property-provider": "^2.1.3",
"@smithy/shared-ini-file-loader": "^2.3.4",
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
"@smithy/property-provider": "^2.2.0",
"@smithy/shared-ini-file-loader": "^2.4.0",
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/node-http-handler": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.4.1.tgz",
"integrity": "sha512-HCkb94soYhJMxPCa61wGKgmeKpJ3Gftx1XD6bcWEB2wMV1L9/SkQu/6/ysKBnbOzWRE01FGzwrTxucHypZ8rdg==",
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.5.0.tgz",
"integrity": "sha512-mVGyPBzkkGQsPoxQUbxlEfRjrj6FPyA3u3u2VXGr9hT8wilsoQdZdvKpMBFMB8Crfhv5dNkKHIW0Yyuc7eABqA==",
"dependencies": {
"@smithy/abort-controller": "^2.1.3",
"@smithy/protocol-http": "^3.2.1",
"@smithy/querystring-builder": "^2.1.3",
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
"@smithy/abort-controller": "^2.2.0",
"@smithy/protocol-http": "^3.3.0",
"@smithy/querystring-builder": "^2.2.0",
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/property-provider": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.1.3.tgz",
"integrity": "sha512-bMz3se+ySKWNrgm7eIiQMa2HO/0fl2D0HvLAdg9pTMcpgp4SqOAh6bz7Ik6y7uQqSrk4rLjIKgbQ6yzYgGehCQ==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.2.0.tgz",
"integrity": "sha512-+xiil2lFhtTRzXkx8F053AV46QnIw6e7MV8od5Mi68E1ICOjCeCHw2XfLnDEUHnT9WGUIkwcqavXjfwuJbGlpg==",
"dependencies": {
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/protocol-http": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.2.1.tgz",
"integrity": "sha512-KLrQkEw4yJCeAmAH7hctE8g9KwA7+H2nSJwxgwIxchbp/L0B5exTdOQi9D5HinPLlothoervGmhpYKelZ6AxIA==",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.3.0.tgz",
"integrity": "sha512-Xy5XK1AFWW2nlY/biWZXu6/krgbaf2dg0q492D8M5qthsnU2H+UgFeZLbM76FnH7s6RO/xhQRkj+T6KBO3JzgQ==",
"dependencies": {
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/querystring-builder": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.1.3.tgz",
"integrity": "sha512-kFD3PnNqKELe6m9GRHQw/ftFFSZpnSeQD4qvgDB6BQN6hREHELSosVFUMPN4M3MDKN2jAwk35vXHLoDrNfKu0A==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.2.0.tgz",
"integrity": "sha512-L1kSeviUWL+emq3CUVSgdogoM/D9QMFaqxL/dd0X7PCNWmPXqt+ExtrBjqT0V7HLN03Vs9SuiLrG3zy3JGnE5A==",
"dependencies": {
"@smithy/types": "^2.10.1",
"@smithy/util-uri-escape": "^2.1.1",
"tslib": "^2.5.0"
"@smithy/types": "^2.12.0",
"@smithy/util-uri-escape": "^2.2.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/querystring-parser": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.1.3.tgz",
"integrity": "sha512-3+CWJoAqcBMR+yvz6D+Fc5VdoGFtfenW6wqSWATWajrRMGVwJGPT3Vy2eb2bnMktJc4HU4bpjeovFa566P3knQ==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.2.0.tgz",
"integrity": "sha512-BvHCDrKfbG5Yhbpj4vsbuPV2GgcpHiAkLeIlcA1LtfpMz3jrqizP1+OguSNSj1MwBHEiN+jwNisXLGdajGDQJA==",
"dependencies": {
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/service-error-classification": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.1.3.tgz",
"integrity": "sha512-iUrpSsem97bbXHHT/v3s7vaq8IIeMo6P6cXdeYHrx0wOJpMeBGQF7CB0mbJSiTm3//iq3L55JiEm8rA7CTVI8A==",
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.1.5.tgz",
"integrity": "sha512-uBDTIBBEdAQryvHdc5W8sS5YX7RQzF683XrHePVdFmAgKiMofU15FLSM0/HU03hKTnazdNRFa0YHS7+ArwoUSQ==",
"dependencies": {
"@smithy/types": "^2.10.1"
"@smithy/types": "^2.12.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/shared-ini-file-loader": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.3.4.tgz",
"integrity": "sha512-CiZmPg9GeDKbKmJGEFvJBsJcFnh0AQRzOtQAzj1XEa8N/0/uSN/v1LYzgO7ry8hhO8+9KB7+DhSW0weqBra4Aw==",
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.4.0.tgz",
"integrity": "sha512-WyujUJL8e1B6Z4PBfAqC/aGY1+C7T0w20Gih3yrvJSk97gpiVfB+y7c46T4Nunk+ZngLq0rOIdeVeIklk0R3OA==",
"dependencies": {
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/signature-v4": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.1.3.tgz",
"integrity": "sha512-Jq4iPPdCmJojZTsPePn4r1ULShh6ONkokLuxp1Lnk4Sq7r7rJp4HlA1LbPBq4bD64TIzQezIpr1X+eh5NYkNxw==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.3.0.tgz",
"integrity": "sha512-ui/NlpILU+6HAQBfJX8BBsDXuKSNrjTSuOYArRblcrErwKFutjrCNb/OExfVRyj9+26F9J+ZmfWT+fKWuDrH3Q==",
"dependencies": {
"@smithy/eventstream-codec": "^2.1.3",
"@smithy/is-array-buffer": "^2.1.1",
"@smithy/types": "^2.10.1",
"@smithy/util-hex-encoding": "^2.1.1",
"@smithy/util-middleware": "^2.1.3",
"@smithy/util-uri-escape": "^2.1.1",
"@smithy/util-utf8": "^2.1.1",
"tslib": "^2.5.0"
"@smithy/is-array-buffer": "^2.2.0",
"@smithy/types": "^2.12.0",
"@smithy/util-hex-encoding": "^2.2.0",
"@smithy/util-middleware": "^2.2.0",
"@smithy/util-uri-escape": "^2.2.0",
"@smithy/util-utf8": "^2.3.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/smithy-client": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.4.2.tgz",
"integrity": "sha512-ntAFYN51zu3N3mCd95YFcFi/8rmvm//uX+HnK24CRbI6k5Rjackn0JhgKz5zOx/tbNvOpgQIwhSX+1EvEsBLbA==",
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.5.1.tgz",
"integrity": "sha512-jrbSQrYCho0yDaaf92qWgd+7nAeap5LtHTI51KXqmpIFCceKU3K9+vIVTUH72bOJngBMqa4kyu1VJhRcSrk/CQ==",
"dependencies": {
"@smithy/middleware-endpoint": "^2.4.4",
"@smithy/middleware-stack": "^2.1.3",
"@smithy/protocol-http": "^3.2.1",
"@smithy/types": "^2.10.1",
"@smithy/util-stream": "^2.1.3",
"tslib": "^2.5.0"
"@smithy/middleware-endpoint": "^2.5.1",
"@smithy/middleware-stack": "^2.2.0",
"@smithy/protocol-http": "^3.3.0",
"@smithy/types": "^2.12.0",
"@smithy/util-stream": "^2.2.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/types": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.10.1.tgz",
"integrity": "sha512-hjQO+4ru4cQ58FluQvKKiyMsFg0A6iRpGm2kqdH8fniyNd2WyanoOsYJfMX/IFLuLxEoW6gnRkNZy1y6fUUhtA==",
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.12.0.tgz",
"integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==",
"dependencies": {
"tslib": "^2.5.0"
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/url-parser": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.1.3.tgz",
"integrity": "sha512-X1NRA4WzK/ihgyzTpeGvI9Wn45y8HmqF4AZ/FazwAv8V203Ex+4lXqcYI70naX9ETqbqKVzFk88W6WJJzCggTQ==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.2.0.tgz",
"integrity": "sha512-hoA4zm61q1mNTpksiSWp2nEl1dt3j726HdRhiNgVJQMj7mLp7dprtF57mOB6JvEk/x9d2bsuL5hlqZbBuHQylQ==",
"dependencies": {
"@smithy/querystring-parser": "^2.1.3",
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
"@smithy/querystring-parser": "^2.2.0",
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
}
},
"node_modules/@smithy/util-base64": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.1.1.tgz",
"integrity": "sha512-UfHVpY7qfF/MrgndI5PexSKVTxSZIdz9InghTFa49QOvuu9I52zLPLUHXvHpNuMb1iD2vmc6R+zbv/bdMipR/g==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.3.0.tgz",
"integrity": "sha512-s3+eVwNeJuXUwuMbusncZNViuhv2LjVJ1nMwTqSA0XAC7gjKhqqxRdJPhR8+YrkoZ9IiIbFk/yK6ACe/xlF+hw==",
"dependencies": {
"@smithy/util-buffer-from": "^2.1.1",
"tslib": "^2.5.0"
"@smithy/util-buffer-from": "^2.2.0",
"@smithy/util-utf8": "^2.3.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-body-length-browser": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.1.1.tgz",
"integrity": "sha512-ekOGBLvs1VS2d1zM2ER4JEeBWAvIOUKeaFch29UjjJsxmZ/f0L3K3x0dEETgh3Q9bkZNHgT+rkdl/J/VUqSRag==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.2.0.tgz",
"integrity": "sha512-dtpw9uQP7W+n3vOtx0CfBD5EWd7EPdIdsQnWTDoFf77e3VUf05uA7R7TGipIo8e4WL2kuPdnsr3hMQn9ziYj5w==",
"dependencies": {
"tslib": "^2.5.0"
"tslib": "^2.6.2"
}
},
"node_modules/@smithy/util-body-length-node": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.2.1.tgz",
"integrity": "sha512-/ggJG+ta3IDtpNVq4ktmEUtOkH1LW64RHB5B0hcr5ZaWBmo96UX2cIOVbjCqqDickTXqBWZ4ZO0APuaPrD7Abg==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.3.0.tgz",
"integrity": "sha512-ITWT1Wqjubf2CJthb0BuT9+bpzBfXeMokH/AAa5EJQgbv9aPMVfnM76iFIZVFf50hYXGbtiV71BHAthNWd6+dw==",
"dependencies": {
"tslib": "^2.5.0"
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-buffer-from": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.1.1.tgz",
"integrity": "sha512-clhNjbyfqIv9Md2Mg6FffGVrJxw7bgK7s3Iax36xnfVj6cg0fUG7I4RH0XgXJF8bxi+saY5HR21g2UPKSxVCXg==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
"integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
"dependencies": {
"@smithy/is-array-buffer": "^2.1.1",
"tslib": "^2.5.0"
"@smithy/is-array-buffer": "^2.2.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-config-provider": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.2.1.tgz",
"integrity": "sha512-50VL/tx9oYYcjJn/qKqNy7sCtpD0+s8XEBamIFo4mFFTclKMNp+rsnymD796uybjiIquB7VCB/DeafduL0y2kw==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.3.0.tgz",
"integrity": "sha512-HZkzrRcuFN1k70RLqlNK4FnPXKOpkik1+4JaBoHNJn+RnJGYqaa3c5/+XtLOXhlKzlRgNvyaLieHTW2VwGN0VQ==",
"dependencies": {
"tslib": "^2.5.0"
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-defaults-mode-browser": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.1.4.tgz",
"integrity": "sha512-J6XAVY+/g7jf03QMnvqPyU+8jqGrrtXoKWFVOS+n1sz0Lg8HjHJ1ANqaDN+KTTKZRZlvG8nU5ZrJOUL6VdwgcQ==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.2.1.tgz",
"integrity": "sha512-RtKW+8j8skk17SYowucwRUjeh4mCtnm5odCL0Lm2NtHQBsYKrNW0od9Rhopu9wF1gHMfHeWF7i90NwBz/U22Kw==",
"dependencies": {
"@smithy/property-provider": "^2.1.3",
"@smithy/smithy-client": "^2.4.2",
"@smithy/types": "^2.10.1",
"@smithy/property-provider": "^2.2.0",
"@smithy/smithy-client": "^2.5.1",
"@smithy/types": "^2.12.0",
"bowser": "^2.11.0",
"tslib": "^2.5.0"
"tslib": "^2.6.2"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/@smithy/util-defaults-mode-node": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.2.3.tgz",
"integrity": "sha512-ttUISrv1uVOjTlDa3nznX33f0pthoUlP+4grhTvOzcLhzArx8qHB94/untGACOG3nlf8vU20nI2iWImfzoLkYA==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.3.1.tgz",
"integrity": "sha512-vkMXHQ0BcLFysBMWgSBLSk3+leMpFSyyFj8zQtv5ZyUBx8/owVh1/pPEkzmW/DR/Gy/5c8vjLDD9gZjXNKbrpA==",
"dependencies": {
"@smithy/config-resolver": "^2.1.4",
"@smithy/credential-provider-imds": "^2.2.4",
"@smithy/node-config-provider": "^2.2.4",
"@smithy/property-provider": "^2.1.3",
"@smithy/smithy-client": "^2.4.2",
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
"@smithy/config-resolver": "^2.2.0",
"@smithy/credential-provider-imds": "^2.3.0",
"@smithy/node-config-provider": "^2.3.0",
"@smithy/property-provider": "^2.2.0",
"@smithy/smithy-client": "^2.5.1",
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/@smithy/util-endpoints": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.1.4.tgz",
"integrity": "sha512-/qAeHmK5l4yQ4/bCIJ9p49wDe9rwWtOzhPHblu386fwPNT3pxmodgcs9jDCV52yK9b4rB8o9Sj31P/7Vzka1cg==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.2.0.tgz",
"integrity": "sha512-BuDHv8zRjsE5zXd3PxFXFknzBG3owCpjq8G3FcsXW3CykYXuEqM3nTSsmLzw5q+T12ZYuDlVUZKBdpNbhVtlrQ==",
"dependencies": {
"@smithy/node-config-provider": "^2.2.4",
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
"@smithy/node-config-provider": "^2.3.0",
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@smithy/util-hex-encoding": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.1.1.tgz",
"integrity": "sha512-3UNdP2pkYUUBGEXzQI9ODTDK+Tcu1BlCyDBaRHwyxhA+8xLP8agEKQq4MGmpjqb4VQAjq9TwlCQX0kP6XDKYLg==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.2.0.tgz",
"integrity": "sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ==",
"dependencies": {
"tslib": "^2.5.0"
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-middleware": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.1.3.tgz",
"integrity": "sha512-/+2fm7AZ2ozl5h8wM++ZP0ovE9/tiUUAHIbCfGfb3Zd3+Dyk17WODPKXBeJ/TnK5U+x743QmA0xHzlSm8I/qhw==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.2.0.tgz",
"integrity": "sha512-L1qpleXf9QD6LwLCJ5jddGkgWyuSvWBkJwWAZ6kFkdifdso+sk3L3O1HdmPvCdnCK3IS4qWyPxev01QMnfHSBw==",
"dependencies": {
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-retry": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.1.3.tgz",
"integrity": "sha512-Kbvd+GEMuozbNUU3B89mb99tbufwREcyx2BOX0X2+qHjq6Gvsah8xSDDgxISDwcOHoDqUWO425F0Uc/QIRhYkg==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.2.0.tgz",
"integrity": "sha512-q9+pAFPTfftHXRytmZ7GzLFFrEGavqapFc06XxzZFcSIGERXMerXxCitjOG1prVDR9QdjqotF40SWvbqcCpf8g==",
"dependencies": {
"@smithy/service-error-classification": "^2.1.3",
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
"@smithy/service-error-classification": "^2.1.5",
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@smithy/util-stream": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.1.3.tgz",
"integrity": "sha512-HvpEQbP8raTy9n86ZfXiAkf3ezp1c3qeeO//zGqwZdrfaoOpGKQgF2Sv1IqZp7wjhna7pvczWaGUHjcOPuQwKw==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.2.0.tgz",
"integrity": "sha512-17faEXbYWIRst1aU9SvPZyMdWmqIrduZjVOqCPMIsWFNxs5yQQgFrJL6b2SdiCzyW9mJoDjFtgi53xx7EH+BXA==",
"dependencies": {
"@smithy/fetch-http-handler": "^2.4.3",
"@smithy/node-http-handler": "^2.4.1",
"@smithy/types": "^2.10.1",
"@smithy/util-base64": "^2.1.1",
"@smithy/util-buffer-from": "^2.1.1",
"@smithy/util-hex-encoding": "^2.1.1",
"@smithy/util-utf8": "^2.1.1",
"tslib": "^2.5.0"
"@smithy/fetch-http-handler": "^2.5.0",
"@smithy/node-http-handler": "^2.5.0",
"@smithy/types": "^2.12.0",
"@smithy/util-base64": "^2.3.0",
"@smithy/util-buffer-from": "^2.2.0",
"@smithy/util-hex-encoding": "^2.2.0",
"@smithy/util-utf8": "^2.3.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-uri-escape": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.1.1.tgz",
"integrity": "sha512-saVzI1h6iRBUVSqtnlOnc9ssU09ypo7n+shdQ8hBTZno/9rZ3AuRYvoHInV57VF7Qn7B+pFJG7qTzFiHxWlWBw==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.2.0.tgz",
"integrity": "sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA==",
"dependencies": {
"tslib": "^2.5.0"
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-utf8": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.1.1.tgz",
"integrity": "sha512-BqTpzYEcUMDwAKr7/mVRUtHDhs6ZoXDi9NypMvMfOr/+u1NW7JgqodPDECiiLboEm6bobcPcECxzjtQh865e9A==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
"integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
"dependencies": {
"@smithy/util-buffer-from": "^2.1.1",
"tslib": "^2.5.0"
"@smithy/util-buffer-from": "^2.2.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"

View File

@ -32,6 +32,7 @@ import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-se
import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
import { TIdentityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
import { TIdentityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service";
import { TIntegrationServiceFactory } from "@app/services/integration/integration-service";
@ -115,6 +116,7 @@ declare module "fastify" {
identityAccessToken: TIdentityAccessTokenServiceFactory;
identityProject: TIdentityProjectServiceFactory;
identityUa: TIdentityUaServiceFactory;
identityAwsAuth: TIdentityAwsAuthServiceFactory;
accessApprovalPolicy: TAccessApprovalPolicyServiceFactory;
accessApprovalRequest: TAccessApprovalRequestServiceFactory;
secretApprovalPolicy: TSecretApprovalPolicyServiceFactory;

View File

@ -59,6 +59,9 @@ import {
TIdentityAccessTokens,
TIdentityAccessTokensInsert,
TIdentityAccessTokensUpdate,
TIdentityAwsAuths,
TIdentityAwsAuthsInsert,
TIdentityAwsAuthsUpdate,
TIdentityOrgMemberships,
TIdentityOrgMembershipsInsert,
TIdentityOrgMembershipsUpdate,
@ -326,6 +329,11 @@ declare module "knex/types/tables" {
TIdentityUniversalAuthsInsert,
TIdentityUniversalAuthsUpdate
>;
[TableName.IdentityAwsAuth]: Knex.CompositeTableType<
TIdentityAwsAuths,
TIdentityAwsAuthsInsert,
TIdentityAwsAuthsUpdate
>;
[TableName.IdentityUaClientSecret]: Knex.CompositeTableType<
TIdentityUaClientSecrets,
TIdentityUaClientSecretsInsert,

View File

@ -0,0 +1,30 @@
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.IdentityAwsAuth))) {
await knex.schema.createTable(TableName.IdentityAwsAuth, (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("stsEndpoint").notNullable();
t.string("allowedPrincipalArns").notNullable();
t.string("allowedAccountIds").notNullable();
});
}
await createOnUpdateTrigger(knex, TableName.IdentityAwsAuth);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityAwsAuth);
await dropOnUpdateTrigger(knex, TableName.IdentityAwsAuth);
}

View File

@ -11,8 +11,8 @@ export const AccessApprovalPoliciesSchema = z.object({
id: z.string().uuid(),
name: z.string(),
approvals: z.number().default(1),
envId: z.string().uuid(),
secretPath: z.string().nullable().optional(),
envId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});

View File

@ -0,0 +1,27 @@
// 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 IdentityAwsAuthsSchema = 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(),
stsEndpoint: z.string(),
allowedPrincipalArns: z.string(),
allowedAccountIds: z.string()
});
export type TIdentityAwsAuths = z.infer<typeof IdentityAwsAuthsSchema>;
export type TIdentityAwsAuthsInsert = Omit<z.input<typeof IdentityAwsAuthsSchema>, TImmutableDBKeys>;
export type TIdentityAwsAuthsUpdate = Partial<Omit<z.input<typeof IdentityAwsAuthsSchema>, TImmutableDBKeys>>;

View File

@ -17,6 +17,7 @@ export * from "./group-project-memberships";
export * from "./groups";
export * from "./identities";
export * from "./identity-access-tokens";
export * from "./identity-aws-auths";
export * from "./identity-org-memberships";
export * from "./identity-project-additional-privilege";
export * from "./identity-project-membership-role";

View File

@ -45,6 +45,7 @@ export enum TableName {
IdentityAccessToken = "identity_access_tokens",
IdentityUniversalAuth = "identity_universal_auths",
IdentityUaClientSecret = "identity_ua_client_secrets",
IdentityAwsAuth = "identity_aws_auths",
IdentityOrgMembership = "identity_org_memberships",
IdentityProjectMembership = "identity_project_memberships",
IdentityProjectMembershipRole = "identity_project_membership_role",
@ -142,5 +143,6 @@ export enum ProjectUpgradeStatus {
}
export enum IdentityAuthMethod {
Univeral = "universal-auth"
Univeral = "universal-auth",
AWS_AUTH = "aws-auth"
}

View File

@ -22,7 +22,7 @@ export const UsersSchema = z.object({
updatedAt: z.date(),
isGhost: z.boolean().default(false),
username: z.string(),
isEmailVerified: z.boolean().nullable().optional()
isEmailVerified: z.boolean().default(false).nullable().optional()
});
export type TUsers = z.infer<typeof UsersSchema>;

View File

@ -66,6 +66,10 @@ export enum EventType {
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth",
CREATE_ENVIRONMENT = "create-environment",
UPDATE_ENVIRONMENT = "update-environment",
DELETE_ENVIRONMENT = "delete-environment",
@ -406,6 +410,50 @@ interface RevokeIdentityUniversalAuthClientSecretEvent {
};
}
interface LoginIdentityAwsAuthEvent {
type: EventType.LOGIN_IDENTITY_AWS_AUTH;
metadata: {
identityId: string;
identityAwsAuthId: string;
identityAccessTokenId: string;
};
}
interface AddIdentityAwsAuthEvent {
type: EventType.ADD_IDENTITY_AWS_AUTH;
metadata: {
identityId: string;
stsEndpoint: string;
allowedPrincipalArns: string;
allowedAccountIds: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: Array<TIdentityTrustedIp>;
};
}
interface UpdateIdentityAwsAuthEvent {
type: EventType.UPDATE_IDENTITY_AWS_AUTH;
metadata: {
identityId: string;
stsEndpoint?: string;
allowedPrincipalArns?: string;
allowedAccountIds?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
};
}
interface GetIdentityAwsAuthEvent {
type: EventType.GET_IDENTITY_AWS_AUTH;
metadata: {
identityId: string;
};
}
interface CreateEnvironmentEvent {
type: EventType.CREATE_ENVIRONMENT;
metadata: {
@ -660,6 +708,10 @@ export type Event =
| CreateIdentityUniversalAuthClientSecretEvent
| GetIdentityUniversalAuthClientSecretsEvent
| RevokeIdentityUniversalAuthClientSecretEvent
| LoginIdentityAwsAuthEvent
| AddIdentityAwsAuthEvent
| UpdateIdentityAwsAuthEvent
| GetIdentityAwsAuthEvent
| CreateEnvironmentEvent
| UpdateEnvironmentEvent
| DeleteEnvironmentEvent

View File

@ -92,6 +92,18 @@ export const UNIVERSAL_AUTH = {
}
} as const;
export const AWS_AUTH = {
LOGIN: {
identityId: "The ID of the identity to login.",
iamHttpRequestMethod: "The HTTP request method used in the signed request.",
iamRequestUrl:
"The base64-encoded HTTP URL used in the signed request. Most likely, the base64-encoding of https://sts.amazonaws.com/",
iamRequestBody:
"The base64-encoded body of the signed request. Most likely, the base64-encoding of Action=GetCallerIdentity&Version=2011-06-15.",
iamRequestHeaders: "The base64-encoded headers of the sts:GetCallerIdentity signed request."
}
} as const;
export const ORGANIZATIONS = {
LIST_USER_MEMBERSHIPS: {
organizationId: "The ID of the organization to get memberships from."

View File

@ -78,6 +78,8 @@ import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal";
import { identityServiceFactory } from "@app/services/identity/identity-service";
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 { identityAwsAuthDALFactory } from "@app/services/identity-aws-auth/identity-aws-auth-dal";
import { identityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
import { identityProjectMembershipRoleDALFactory } from "@app/services/identity-project/identity-project-membership-role-dal";
import { identityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
@ -201,6 +203,7 @@ export const registerRoutes = async (
const identityUaDAL = identityUaDALFactory(db);
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
const identityAwsAuthDAL = identityAwsAuthDALFactory(db);
const auditLogDAL = auditLogDALFactory(db);
const auditLogStreamDAL = auditLogStreamDALFactory(db);
@ -699,6 +702,14 @@ export const registerRoutes = async (
identityUaDAL,
licenseService
});
const identityAwsAuthService = identityAwsAuthServiceFactory({
identityAccessTokenDAL,
identityAwsAuthDAL,
identityOrgMembershipDAL,
identityDAL,
licenseService,
permissionService
});
const dynamicSecretProviders = buildDynamicSecretProviders();
const dynamicSecretQueueService = dynamicSecretLeaseQueueServiceFactory({
@ -768,6 +779,7 @@ export const registerRoutes = async (
identityAccessToken: identityAccessTokenService,
identityProject: identityProjectService,
identityUa: identityUaService,
identityAwsAuth: identityAwsAuthService,
secretApprovalPolicy: sapService,
accessApprovalPolicy: accessApprovalPolicyService,
accessApprovalRequest: accessApprovalRequestService,

View File

@ -0,0 +1,269 @@
import { z } from "zod";
import { IdentityAwsAuthsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { AWS_AUTH } 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 {
validateAccountIds,
validatePrincipalArns
} from "@app/services/identity-aws-auth/identity-aws-auth-validators";
export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/aws-auth/login",
config: {
rateLimit: writeLimit
},
schema: {
description: "Login with AWS Auth",
body: z.object({
identityId: z.string().describe(AWS_AUTH.LOGIN.identityId),
iamHttpRequestMethod: z.string().default("POST").describe(AWS_AUTH.LOGIN.iamHttpRequestMethod),
iamRequestBody: z.string().describe(AWS_AUTH.LOGIN.iamRequestBody),
iamRequestHeaders: z.string().describe(AWS_AUTH.LOGIN.iamRequestHeaders)
}),
response: {
200: z.object({
accessToken: z.string(),
expiresIn: z.coerce.number(),
accessTokenMaxTTL: z.coerce.number(),
tokenType: z.literal("Bearer")
})
}
},
handler: async (req) => {
const { identityAwsAuth, accessToken, identityAccessToken, identityMembershipOrg } =
await server.services.identityAwsAuth.login(req.body);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
event: {
type: EventType.LOGIN_IDENTITY_AWS_AUTH,
metadata: {
identityId: identityAwsAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
identityAwsAuthId: identityAwsAuth.id
}
}
});
return {
accessToken,
tokenType: "Bearer" as const,
expiresIn: identityAwsAuth.accessTokenTTL,
accessTokenMaxTTL: identityAwsAuth.accessTokenMaxTTL
};
}
});
server.route({
method: "POST",
url: "/aws-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Attach AWS Auth configuration onto identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().trim()
}),
body: z.object({
stsEndpoint: z.string().trim().min(1).default("https://sts.amazonaws.com/"),
allowedPrincipalArns: validatePrincipalArns,
allowedAccountIds: validateAccountIds,
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
accessTokenTTL: z
.number()
.int()
.min(1)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
.default(2592000),
accessTokenMaxTTL: z
.number()
.int()
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
.default(2592000),
accessTokenNumUsesLimit: z.number().int().min(0).default(0)
}),
response: {
200: z.object({
identityAwsAuth: IdentityAwsAuthsSchema
})
}
},
handler: async (req) => {
const identityAwsAuth = await server.services.identityAwsAuth.attachAwsAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityAwsAuth.orgId,
event: {
type: EventType.ADD_IDENTITY_AWS_AUTH,
metadata: {
identityId: identityAwsAuth.identityId,
stsEndpoint: identityAwsAuth.stsEndpoint,
allowedPrincipalArns: identityAwsAuth.allowedPrincipalArns,
allowedAccountIds: identityAwsAuth.allowedAccountIds,
accessTokenTTL: identityAwsAuth.accessTokenTTL,
accessTokenMaxTTL: identityAwsAuth.accessTokenMaxTTL,
accessTokenTrustedIps: identityAwsAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityAwsAuth.accessTokenNumUsesLimit
}
}
});
return { identityAwsAuth };
}
});
server.route({
method: "PATCH",
url: "/aws-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Update AWS Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string()
}),
body: z.object({
stsEndpoint: z.string().trim().min(1).optional(),
allowedPrincipalArns: validatePrincipalArns,
allowedAccountIds: validateAccountIds,
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.optional(),
accessTokenTTL: z.number().int().min(0).optional(),
accessTokenNumUsesLimit: z.number().int().min(0).optional(),
accessTokenMaxTTL: z
.number()
.int()
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
.optional()
}),
response: {
200: z.object({
identityAwsAuth: IdentityAwsAuthsSchema
})
}
},
handler: async (req) => {
const identityAwsAuth = await server.services.identityAwsAuth.updateAwsAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityAwsAuth.orgId,
event: {
type: EventType.UPDATE_IDENTITY_AWS_AUTH,
metadata: {
identityId: identityAwsAuth.identityId,
stsEndpoint: identityAwsAuth.stsEndpoint,
allowedPrincipalArns: identityAwsAuth.allowedPrincipalArns,
allowedAccountIds: identityAwsAuth.allowedAccountIds,
accessTokenTTL: identityAwsAuth.accessTokenTTL,
accessTokenMaxTTL: identityAwsAuth.accessTokenMaxTTL,
accessTokenTrustedIps: identityAwsAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityAwsAuth.accessTokenNumUsesLimit
}
}
});
return { identityAwsAuth };
}
});
server.route({
method: "GET",
url: "/aws-auth/identities/:identityId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Retrieve AWS Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string()
}),
response: {
200: z.object({
identityAwsAuth: IdentityAwsAuthsSchema
})
}
},
handler: async (req) => {
const identityAwsAuth = await server.services.identityAwsAuth.getAwsAuth({
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: identityAwsAuth.orgId,
event: {
type: EventType.GET_IDENTITY_AWS_AUTH,
metadata: {
identityId: identityAwsAuth.identityId
}
}
});
return { identityAwsAuth };
}
});
};

View File

@ -2,6 +2,7 @@ import { registerAdminRouter } from "./admin-router";
import { registerAuthRoutes } from "./auth-router";
import { registerProjectBotRouter } from "./bot-router";
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
import { registerIdentityAwsAuthRouter } from "./identity-aws-iam-auth-router";
import { registerIdentityRouter } from "./identity-router";
import { registerIdentityUaRouter } from "./identity-ua";
import { registerIntegrationAuthRouter } from "./integration-auth-router";
@ -28,6 +29,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
await authRouter.register(registerAuthRoutes);
await authRouter.register(registerIdentityUaRouter);
await authRouter.register(registerIdentityAccessTokenRouter);
await authRouter.register(registerIdentityAwsAuthRouter);
},
{ prefix: "/auth" }
);

View File

@ -0,0 +1,11 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TIdentityAwsAuthDALFactory = ReturnType<typeof identityAwsAuthDALFactory>;
export const identityAwsAuthDALFactory = (db: TDbClient) => {
const awsAuthOrm = ormify(db, TableName.IdentityAwsAuth);
return awsAuthOrm;
};

View File

@ -0,0 +1,67 @@
/**
* Extracts the identity ARN from the GetCallerIdentity response to one of the following formats:
* - arn:aws:iam::123456789012:user/MyUserName
* - arn:aws:iam::123456789012:role/MyRoleName
*/
export const extractPrincipalArn = (arn: string) => {
// split the ARN into parts using ":" as the delimiter
const fullParts = arn.split(":");
if (fullParts.length !== 6) {
throw new Error(`Unrecognized ARN: contains ${fullParts.length} colon-separated parts, expected 6`);
}
const [prefix, partition, service, , accountNumber, resource] = fullParts;
if (prefix !== "arn") {
throw new Error('Unrecognized ARN: does not begin with "arn:"');
}
// structure to hold the parsed data
const entity = {
Partition: partition,
Service: service,
AccountNumber: accountNumber,
Type: "",
Path: "",
FriendlyName: "",
SessionInfo: ""
};
// validate the service is either 'iam' or 'sts'
if (entity.Service !== "iam" && entity.Service !== "sts") {
throw new Error(`Unrecognized service: ${entity.Service}, not one of iam or sts`);
}
// parse the last part of the ARN which describes the resource
const parts = resource.split("/");
if (parts.length < 2) {
throw new Error(`Unrecognized ARN: "${resource}" contains fewer than 2 slash-separated parts`);
}
const [type, ...rest] = parts;
entity.Type = type;
entity.FriendlyName = parts[parts.length - 1];
// handle different types of resources
switch (entity.Type) {
case "assumed-role": {
if (rest.length < 2) {
throw new Error(`Unrecognized ARN: "${resource}" contains fewer than 3 slash-separated parts`);
}
// assumed roles use a special format where the friendly name is the role name
const [roleName, sessionId] = rest;
entity.Type = "role"; // treat assumed role case as role
entity.FriendlyName = roleName;
entity.SessionInfo = sessionId;
break;
}
case "user":
case "role":
case "instance-profile":
// standard cases: just join back the path if there's any
entity.Path = rest.slice(0, -1).join("/");
break;
default:
throw new Error(`Unrecognized principal type: "${entity.Type}"`);
}
return `arn:aws:iam::${entity.AccountNumber}:${entity.Type}/${entity.FriendlyName}`;
};

View File

@ -0,0 +1,310 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ForbiddenError } from "@casl/ability";
import axios from "axios";
import jwt from "jsonwebtoken";
import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
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 { TIdentityAwsAuthDALFactory } from "./identity-aws-auth-dal";
import { extractPrincipalArn } from "./identity-aws-auth-fns";
import {
TAttachAwsAuthDTO,
TAwsGetCallerIdentityHeaders,
TGetAwsAuthDTO,
TGetCallerIdentityResponse,
TLoginAwsAuthDTO,
TUpdateAwsAuthDTO
} from "./identity-aws-auth-types";
type TIdentityAwsAuthServiceFactoryDep = {
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
identityAwsAuthDAL: Pick<TIdentityAwsAuthDALFactory, "findOne" | "transaction" | "create" | "updateById">;
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
identityDAL: Pick<TIdentityDALFactory, "updateById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
};
export type TIdentityAwsAuthServiceFactory = ReturnType<typeof identityAwsAuthServiceFactory>;
export const identityAwsAuthServiceFactory = ({
identityAccessTokenDAL,
identityAwsAuthDAL,
identityOrgMembershipDAL,
identityDAL,
licenseService,
permissionService
}: TIdentityAwsAuthServiceFactoryDep) => {
const login = async ({ identityId, iamHttpRequestMethod, iamRequestBody, iamRequestHeaders }: TLoginAwsAuthDTO) => {
const identityAwsAuth = await identityAwsAuthDAL.findOne({ identityId });
if (!identityAwsAuth) throw new UnauthorizedError();
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityAwsAuth.identityId });
const headers: TAwsGetCallerIdentityHeaders = JSON.parse(Buffer.from(iamRequestHeaders, "base64").toString());
const body: string = Buffer.from(iamRequestBody, "base64").toString();
const {
data: {
GetCallerIdentityResponse: {
GetCallerIdentityResult: { Account, Arn }
}
}
}: { data: TGetCallerIdentityResponse } = await axios({
method: iamHttpRequestMethod,
url: identityAwsAuth.stsEndpoint,
headers,
data: body
});
if (identityAwsAuth.allowedAccountIds) {
// validate if Account is in the list of allowed Account IDs
const isAccountAllowed = identityAwsAuth.allowedAccountIds
.split(",")
.map((accountId) => accountId.trim())
.some((accountId) => accountId === Account);
if (!isAccountAllowed) throw new UnauthorizedError();
}
if (identityAwsAuth.allowedPrincipalArns) {
// validate if Arn is in the list of allowed Principal ARNs
const isArnAllowed = identityAwsAuth.allowedPrincipalArns
.split(",")
.map((principalArn) => principalArn.trim())
.some((principalArn) => {
// convert wildcard ARN to a regular expression: "arn:aws:iam::123456789012:*" -> "^arn:aws:iam::123456789012:.*$"
// considers exact matches + wildcard matches
const regex = new RegExp(`^${principalArn.replace(/\*/g, ".*")}$`);
return regex.test(extractPrincipalArn(Arn));
});
if (!isArnAllowed) throw new UnauthorizedError();
}
const identityAccessToken = await identityAwsAuthDAL.transaction(async (tx) => {
const newToken = await identityAccessTokenDAL.create(
{
identityId: identityAwsAuth.identityId,
isAccessTokenRevoked: false,
accessTokenTTL: identityAwsAuth.accessTokenTTL,
accessTokenMaxTTL: identityAwsAuth.accessTokenMaxTTL,
accessTokenNumUses: 0,
accessTokenNumUsesLimit: identityAwsAuth.accessTokenNumUsesLimit
},
tx
);
return newToken;
});
const appCfg = getConfig();
const accessToken = jwt.sign(
{
identityId: identityAwsAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
} as TIdentityAccessTokenJwtPayload,
appCfg.AUTH_SECRET,
{
expiresIn:
Number(identityAccessToken.accessTokenMaxTTL) === 0
? undefined
: Number(identityAccessToken.accessTokenMaxTTL)
}
);
return { accessToken, identityAwsAuth, identityAccessToken, identityMembershipOrg };
};
const attachAwsAuth = async ({
identityId,
stsEndpoint,
allowedPrincipalArns,
allowedAccountIds,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps,
actorId,
actorAuthMethod,
actor,
actorOrgId
}: TAttachAwsAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity.authMethod)
throw new BadRequestError({
message: "Failed to add AWS 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(OrgPermissionActions.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 identityAwsAuth = await identityAwsAuthDAL.transaction(async (tx) => {
const doc = await identityAwsAuthDAL.create(
{
identityId: identityMembershipOrg.identityId,
type: "iam",
stsEndpoint,
allowedPrincipalArns,
allowedAccountIds,
accessTokenMaxTTL,
accessTokenTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps)
},
tx
);
await identityDAL.updateById(
identityMembershipOrg.identityId,
{
authMethod: IdentityAuthMethod.AWS_AUTH
},
tx
);
return doc;
});
return { ...identityAwsAuth, orgId: identityMembershipOrg.orgId };
};
const updateAwsAuth = async ({
identityId,
stsEndpoint,
allowedPrincipalArns,
allowedAccountIds,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps,
actorId,
actorAuthMethod,
actor,
actorOrgId
}: TUpdateAwsAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AWS_AUTH)
throw new BadRequestError({
message: "Failed to update AWS Auth"
});
const identityAwsAuth = await identityAwsAuthDAL.findOne({ identityId });
if (
(accessTokenMaxTTL || identityAwsAuth.accessTokenMaxTTL) > 0 &&
(accessTokenTTL || identityAwsAuth.accessTokenMaxTTL) > (accessTokenMaxTTL || identityAwsAuth.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(OrgPermissionActions.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 updatedAwsAuth = await identityAwsAuthDAL.updateById(identityAwsAuth.id, {
stsEndpoint,
allowedPrincipalArns,
allowedAccountIds,
accessTokenMaxTTL,
accessTokenTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps: reformattedAccessTokenTrustedIps
? JSON.stringify(reformattedAccessTokenTrustedIps)
: undefined
});
return { ...updatedAwsAuth, orgId: identityMembershipOrg.orgId };
};
const getAwsAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetAwsAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AWS_AUTH)
throw new BadRequestError({
message: "The identity does not have AWS Auth attached"
});
const awsIdentityAuth = await identityAwsAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
return { ...awsIdentityAuth, orgId: identityMembershipOrg.orgId };
};
return {
login,
attachAwsAuth,
updateAwsAuth,
getAwsAuth
};
};

View File

@ -0,0 +1,54 @@
import { TProjectPermission } from "@app/lib/types";
export type TLoginAwsAuthDTO = {
identityId: string;
iamHttpRequestMethod: string;
iamRequestBody: string;
iamRequestHeaders: string;
};
export type TAttachAwsAuthDTO = {
identityId: string;
stsEndpoint: string;
allowedPrincipalArns: string;
allowedAccountIds: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: { ipAddress: string }[];
} & Omit<TProjectPermission, "projectId">;
export type TUpdateAwsAuthDTO = {
identityId: string;
stsEndpoint?: string;
allowedPrincipalArns?: string;
allowedAccountIds?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: { ipAddress: string }[];
} & Omit<TProjectPermission, "projectId">;
export type TGetAwsAuthDTO = {
identityId: string;
} & Omit<TProjectPermission, "projectId">;
export type TAwsGetCallerIdentityHeaders = {
"Content-Type": string;
Host: string;
"X-Amz-Date": string;
"Content-Length": number;
"x-amz-security-token": string;
Authorization: string;
};
export type TGetCallerIdentityResponse = {
GetCallerIdentityResponse: {
GetCallerIdentityResult: {
Account: string;
Arn: string;
UserId: string;
};
ResponseMetadata: { RequestId: string };
};
};

View File

@ -0,0 +1,58 @@
import { z } from "zod";
const twelveDigitRegex = /^\d{12}$/;
const arnRegex = /^arn:aws:iam::\d{12}:(user\/[\w-]+|role\/[\w-]+|\*)$/;
export const validateAccountIds = z
.string()
.trim()
.default("")
// Custom validation to ensure each part is a 12-digit number
.refine(
(data) => {
if (data === "") return true;
// Split the string by commas to check each supposed number
const accountIds = data.split(",").map((id) => id.trim());
// Return true only if every item matches the 12-digit requirement
return accountIds.every((id) => twelveDigitRegex.test(id));
},
{
message: "Each account ID must be a 12-digit number."
}
)
// Transform the string to normalize space after commas
.transform((data) => {
if (data === "") return "";
// Trim each ID and join with ', ' to ensure formatting
return data
.split(",")
.map((id) => id.trim())
.join(", ");
});
export const validatePrincipalArns = z
.string()
.trim()
.default("")
// Custom validation for ARN format
.refine(
(data) => {
// Skip validation if the string is empty
if (data === "") return true;
// Split the string by commas to check each supposed ARN
const arns = data.split(",");
// Return true only if every item matches one of the allowed ARN formats
return arns.every((arn) => arnRegex.test(arn.trim()));
},
{
message:
"Each ARN must be in the format of 'arn:aws:iam::123456789012:user/UserName', 'arn:aws:iam::123456789012:role/RoleName', or 'arn:aws:iam::123456789012:*'."
}
)
// Transform to normalize the spaces around commas
.transform((data) =>
data
.split(",")
.map((arn) => arn.trim())
.join(", ")
);

View File

@ -4,59 +4,66 @@ sidebarTitle: "What is Infisical?"
description: "An Introduction to the Infisical secret management platform."
---
Infisical is an [open-source](https://github.com/infisical/infisical) secret management platform for developers.
It provides capabilities for storing, managing, and syncing application configuration and secrets like API keys, database
credentials, and certificates across infrastructure. In addition, Infisical prevents secrets leaks to git and enables secure
Infisical is an [open-source](https://github.com/infisical/infisical) secret management platform for developers.
It provides capabilities for storing, managing, and syncing application configuration and secrets like API keys, database
credentials, and certificates across infrastructure. In addition, Infisical prevents secrets leaks to git and enables secure
sharing of secrets among engineers.
Start managing secrets securely with [Infisical Cloud](https://app.infisical.com) or learn how to [host Infisical](/self-hosting/overview) yourself.
<CardGroup cols={2}>
<Card
title="Infisical Cloud"
href="https://app.infisical.com/signup"
icon="cloud"
color="#000000"
>
Get started with Infisical Cloud in just a few minutes.
</Card>
<Card
href="/self-hosting/overview"
title="Self-hosting"
icon="server"
color="#000000"
>
Self-host Infisical on your own infrastructure.
</Card>
<Card
title="Infisical Cloud"
href="https://app.infisical.com/signup"
icon="cloud"
color="#000000"
>
Get started with Infisical Cloud in just a few minutes.
</Card>
<Card
href="/self-hosting/overview"
title="Self-hosting"
icon="server"
color="#000000"
>
Self-host Infisical on your own infrastructure.
</Card>
</CardGroup>
## Why Infisical?
## Why Infisical?
Infisical helps developers achieve secure centralized secret management and provides all the tools to easily manage secrets in various environments and infrastructure components. In particular, here are some of the most common points that developers mention after adopting Infisical:
Infisical helps developers achieve secure centralized secret management and provides all the tools to easily manage secrets in various environments and infrastructure components. In particular, here are some of the most common points that developers mention after adopting Infisical:
- Streamlined **local development** processes (switching .env files to [Infisical CLI](/cli/commands/run) and removing secrets from developer machines).
- **Best-in-class developer experience** with an easy-to-use [Web Dashboard](/documentation/platform/project).
- Simple secret management inside **[CI/CD pipelines](/integrations/cicd/githubactions)** and staging environments.
- Secure and compliant secret management practices in **[production environments](/sdks/overview)**.
- **Best-in-class developer experience** with an easy-to-use [Web Dashboard](/documentation/platform/project).
- Simple secret management inside **[CI/CD pipelines](/integrations/cicd/githubactions)** and staging environments.
- Secure and compliant secret management practices in **[production environments](/sdks/overview)**.
- **Facilitated workflows** around [secret change management](/documentation/platform/pr-workflows), [access requests](/documentation/platform/access-controls/access-requests), [temporary access provisioning](/documentation/platform/access-controls/temporary-access), and more.
- **Improved security posture** thanks to [secret scanning](/cli/scanning-overview), [granular access control policies](/documentation/platform/access-controls/overview), [automated secret rotation](https://infisical.com/docs/documentation/platform/secret-rotation/overview), and [dynamic secrets](/documentation/platform/dynamic-secrets/overview) capabilities.
## How does Infisical work?
## How does Infisical work?
To make secret management effortless and secure, Infisical follows a certain structure for enabling secret management workflows as defined below.
To make secret management effortless and secure, Infisical follows a certain structure for enabling secret management workflows as defined below.
**Identities** in Infisical are users or machine which have a certain set of roles and permissions assigned to them. Such identities are able to manage secrets in various **Clients** throughout the entire infrastructure. To do that, identities have to verify themselves through one of the available **Authentication Methods**.
**Identities** in Infisical are users or machine which have a certain set of roles and permissions assigned to them. Such identities are able to manage secrets in various **Clients** throughout the entire infrastructure. To do that, identities have to verify themselves through one of the available **Authentication Methods**.
As a result, the 3 main concepts that are important to understand are:
- **[Identities](/documentation/platform/identities/overview)**: users or machines with a set permissions assigned to them.
As a result, the 3 main concepts that are important to understand are:
- **[Identities](/documentation/platform/identities/overview)**: users or machines with a set permissions assigned to them.
- **[Clients](/integrations/platforms/kubernetes)**: Infisical-developed tools for managing secrets in various infrastructure components (e.g., [Kubernetes Operator](/integrations/platforms/kubernetes), [Infisical Agent](/integrations/platforms/infisical-agent), [CLI](/cli/usage), [SDKs](/sdks/overview), [API](/api-reference/overview/introduction), [Web Dashboard](/documentation/platform/organization)).
- **[Authentication Methods](/documentation/platform/identities/universal-auth)**: ways for Identities to authenticate inside different clients (e.g., SAML SSO for Web Dashboard, Universal Auth for Infisical Agent, etc.).
- **[Authentication Methods](/documentation/platform/identities/universal-auth)**: ways for Identities to authenticate inside different clients (e.g., SAML SSO for Web Dashboard, Universal Auth for Infisical Agent, AWS Auth etc.).
## How to get started with Infisical?
## How to get started with Infisical?
Depending on your use case, it might be helpful to look into some of the resources and guides provided below.
<CardGroup cols={2}>
<Card href="../../cli/overview" title="Command Line Interface (CLI)" icon="square-terminal" color="#000000">
<Card
href="../../cli/overview"
title="Command Line Interface (CLI)"
icon="square-terminal"
color="#000000"
>
Inject secrets into any application process/environment.
</Card>
<Card
@ -67,7 +74,12 @@ Depending on your use case, it might be helpful to look into some of the resourc
>
Fetch secrets with any programming language on demand.
</Card>
<Card href="../../integrations/platforms/docker-intro" title="Docker" icon="docker" color="#000000">
<Card
href="../../integrations/platforms/docker-intro"
title="Docker"
icon="docker"
color="#000000"
>
Inject secrets into Docker containers.
</Card>
<Card

View File

@ -0,0 +1,304 @@
---
title: AWS Auth
description: "Learn how to authenticate with Infisical for EC2 instances, Lambda functions, and other IAM principals."
---
**AWS Auth** is an AWS-native authentication method for IAM principals like EC2 instances or Lambda functions to access Infisical.
## Diagram
The following sequence digram illustrates the AWS Auth workflow for authenticating AWS IAM principals with Infisical.
```mermaid
sequenceDiagram
participant Client as Client
participant Infis as Infisical
participant AWS as AWS STS
Note over Client,Client: Step 1: Sign GetCallerIdentityQuery
Note over Client,Infis: Step 2: Login Operation
Client->>Infis: Send signed query details /api/v1/auth/aws-auth/login
Note over Infis,AWS: Step 3: Query verification
Infis->>AWS: Forward signed GetCallerIdentity query
AWS-->>Infis: Return IAM user/role details
Note over Infis: Step 4: Identity Property Validation
Infis->>Client: Return short-lived access token
Note over Client,Infis: Step 4: Access Infisical API with Token
Client->>Infis: Make authenticated requests using the short-lived access token
```
## Concept
At a high-level, Infisical authenticates an IAM principal by verifying its identity and checking that it meets specific requirements (e.g. it is an allowed IAM principal ARN) at the `/api/v1/auth/aws-auth/login` endpoint. If successful,
then Infisical returns a short-lived access token that can be used to make authenticated requests to the Infisical API.
In AWS Auth, an IAM principal signs a `GetCallerIdentity` query using the [AWS Signature v4 algorithm](https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html); this is done using the credentials from the AWS environment where the IAM principal is running.
The query data including the request method, request body, and request headers are sent to Infisical afterwhich Infisical forwards the signed query to AWS STS API via the [sts:GetCallerIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html) method to verify and obtain the identity of the IAM principal.
Once obtained, the identity information is verified against specified requirements such as if the associated IAM principal ARN is allowed to authenticate with Infisical. If all is well, Infisical returns a short-lived access token that can be used to make authenticated requests to the Infisical API.
<Note>
We recommend using one of Infisical's clients like SDKs or the Infisical Agent
to authenticate with Infisical using AWS Auth as they handle the
authentication process including the signed `GetCallerIdentity` query
construction for you.
Also, note that Infisical needs network-level access to send requests to the AWS STS API
as part of the AWS Auth workflow.
</Note>
## Workflow
In the following steps, we explore how to create and use identities for your workloads and applications on AWS to
access the Infisical API using the AWS Auth authentication method.
<Steps>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)
When creating an identity, you specify an organization level [role](/documentation/platform/role-based-access-controls) for it to assume; you can configure roles in Organization Settings > Access Control > Organization Roles.
![identities organization create](/images/platform/identities/identities-org-create.png)
Now input a few details for your new identity. Here's some guidance for each field:
- Name (required): A friendly name for the identity.
- Role (required): A role from the **Organization Roles** tab for the identity to assume. The organization role assigned will determine what organization level resources this identity can have access to.
Once you've created an identity, you'll be prompted to configure the authentication method for it. Here, select **AWS Auth**.
![identities create aws auth method](/images/platform/identities/identities-org-create-aws-auth-method.png)
Here's some more guidance on each field:
- Allowed Principal ARNs: A comma-separated list of trusted IAM principal ARNs that are allowed to authenticate with Infisical. The values should take one of three forms: `arn:aws:iam::123456789012:user/MyUserName`, `arn:aws:iam::123456789012:role/MyRoleName`, or `arn:aws:iam::123456789012:*`. Using a wildcard in this case allows any IAM principal in the account `123456789012` to authenticate with Infisical under the identity.
- Allowed Account IDs: A comma-separated list of trusted AWS account IDs that are allowed to authenticate with Infisical.
- STS Endpoint (default is `https://sts.amazonaws.com/`): The endpoint URL for the AWS STS API. This is useful for AWS GovCloud or other AWS regions that have different STS endpoints.
- Access Token TTL (default is `2592000` equivalent to 30 days): The lifetime for an acccess token in seconds. This value will be referenced at renewal time.
- Access Token Max TTL (default is `2592000` equivalent to 30 days): The maximum lifetime for an acccess token in seconds. This value will be referenced at renewal time.
- Access Token Max Number of Uses (default is `0`): The maximum number of times that an access token can be used; a value of `0` implies infinite number of uses.
- Access Token Trusted IPs: The IPs or CIDR ranges that access tokens can be used from. By default, each token is given the `0.0.0.0/0`, allowing usage from any network address.
</Step>
<Step title="Adding an identity to a project">
To enable the identity to access project-level resources such as secrets within a specific project, you should add it to that project.
To do this, head over to the project you want to add the identity to and go to Project Settings > Access Control > Machine Identities and press **Add identity**.
Next, select the identity you want to add to the project and the project level role you want to allow it to assume. The project role assigned will determine what project level resources this identity can have access to.
![identities project](/images/platform/identities/identities-project.png)
![identities project create](/images/platform/identities/identities-project-create.png)
</Step>
<Step title="Accessing the Infisical API with the identity">
To access the Infisical API as the identity, you need to construct a signed `GetCallerIdentity` query using the [AWS Signature v4 algorithm](https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html) and make a request to the `/api/v1/auth/aws-auth/login` endpoint containing the query data
in exchange for an access token.
We provide a few code examples below of how you can authenticate with Infisical from inside a Lambda function, EC2 instance, etc. and obtain an access token to access the [Infisical API](/api-reference/overview/introduction).
<AccordionGroup>
<Accordion
title="Sample code for inside a Lambda function"
>
The following query construction is an example of how you can authenticate with Infisical from inside a Lambda function.
The shown example uses Node.js but you can use other languages supported by AWS Lambda.
```javascript
import AWS from "aws-sdk";
import axios from "axios";
export const handler = async (event, context) => {
try {
const region = process.env.AWS_REGION;
AWS.config.update({ region });
const iamRequestURL = `https://sts.${region}.amazonaws.com/`;
const iamRequestBody = "Action=GetCallerIdentity&Version=2011-06-15";
const iamRequestHeaders = {
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
Host: `sts.${region}.amazonaws.com`,
};
// Create the request
const request = new AWS.HttpRequest(iamRequestURL, region);
request.method = "POST";
request.headers = iamRequestHeaders;
request.headers["X-Amz-Date"] = AWS.util.date
.iso8601(new Date())
.replace(/[:-]|\.\d{3}/g, "");
request.body = iamRequestBody;
request.headers["Content-Length"] =
Buffer.byteLength(iamRequestBody).toString();
// Sign the request
const signer = new AWS.Signers.V4(request, "sts");
signer.addAuthorization(AWS.config.credentials, new Date());
const infisicalUrl = "https://app.infisical.com"; // or your self-hosted Infisical URL
const identityId = "<your-identity-id>";
const { data } = await axios.post(
`${infisicalUrl}/api/v1/auth/aws-auth/login`,
{
identityId,
iamHttpRequestMethod: "POST",
iamRequestUrl: Buffer.from(iamRequestURL).toString("base64"),
iamRequestBody: Buffer.from(iamRequestBody).toString("base64"),
iamRequestHeaders: Buffer.from(
JSON.stringify(iamRequestHeaders)
).toString("base64"),
}
);
console.log("result data: ", data); // access token here
} catch (err) {
console.error(err);
}
};
````
</Accordion>
<Accordion
title="Sample code for inside an EC2 instance"
>
The following query construction is an example of how you can authenticate with Infisical from inside a EC2 instance.
The shown example uses Node.js but you can use other language you wish.
```javascript
import AWS from "aws-sdk";
import axios from "axios";
const main = async () => {
try {
// obtain region from EC2 instance metadata
const tokenResponse = await axios.put("http://169.254.169.254/latest/api/token", null, {
headers: {
"X-aws-ec2-metadata-token-ttl-seconds": "21600"
}
});
const url = "http://169.254.169.254/latest/dynamic/instance-identity/document";
const response = await axios.get(url, {
headers: {
"X-aws-ec2-metadata-token": tokenResponse.data
}
});
const region = response.data.region;
AWS.config.update({
region
});
const iamRequestURL = `https://sts.${region}.amazonaws.com/`;
const iamRequestBody = "Action=GetCallerIdentity&Version=2011-06-15";
const iamRequestHeaders = {
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
Host: `sts.${region}.amazonaws.com`
};
const request = new AWS.HttpRequest(new AWS.Endpoint(iamRequestURL), AWS.config.region);
request.method = "POST";
request.headers = iamRequestHeaders;
request.headers["X-Amz-Date"] = AWS.util.date.iso8601(new Date()).replace(/[:-]|\.\d{3}/g, "");
request.body = iamRequestBody;
request.headers["Content-Length"] = Buffer.byteLength(iamRequestBody);
const signer = new AWS.Signers.V4(request, "sts");
signer.addAuthorization(AWS.config.credentials, new Date());
const infisicalUrl = "https://app.infisical.com"; // or your self-hosted Infisical URL
const identityId = "<your-identity-id>";
const { data } = await axios.post(`${infisicalUrl}/api/v1/auth/aws-auth/login`, {
identityId,
iamHttpRequestMethod: "POST",
iamRequestUrl: Buffer.from(iamRequestURL).toString("base64"),
iamRequestBody: Buffer.from(iamRequestBody).toString("base64"),
iamRequestHeaders: Buffer.from(JSON.stringify(iamRequestHeaders)).toString("base64")
});
console.log("result data: ", data); // access token here
} catch (err) {
console.error(err);
}
}
main();
````
</Accordion>
<Accordion
title="Sample code for general query construction"
>
The following query construction provides a generic example of how you can construct a signed `GetCallerIdentity` query and obtain the required payload components.
The shown example uses Node.js but you can use any language you wish.
```javascript
const AWS = require("aws-sdk");
const region = "<your-aws-region>";
const infisicalUrl = "https://app.infisical.com"; // or your self-hosted Infisical URL
const iamRequestURL = `https://sts.${region}.amazonaws.com/`;
const iamRequestBody = "Action=GetCallerIdentity&Version=2011-06-15";
const iamRequestHeaders = {
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
Host: `sts.${region}.amazonaws.com`
};
const request = new AWS.HttpRequest(new AWS.Endpoint(iamRequestURL), region);
request.method = "POST";
request.headers = iamRequestHeaders;
request.headers["X-Amz-Date"] = AWS.util.date.iso8601(new Date()).replace(/[:-]|\.\d{3}/g, "");
request.body = iamRequestBody;
request.headers["Content-Length"] = Buffer.byteLength(iamRequestBody);
````
#### Sample request
```bash Request
curl --location --request POST 'https://app.infisical.com/api/v1/auth/aws-auth/login' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'identityId=...' \
--data-urlencode 'iamHttpRequestMethod=...' \
--data-urlencode 'iamRequestBody=...' \
--data-urlencode 'iamRequestHeaders=...'
```
#### Sample response
```bash Response
{
"accessToken": "...",
"expiresIn": 7200,
"accessTokenMaxTTL": 43244
"tokenType": "Bearer"
}
```
Next, you can use the access token to access the [Infisical API](/api-reference/overview/introduction)
</Accordion>
</AccordionGroup>
<Tip>
We recommend using one of Infisical's clients like SDKs or the Infisical Agent to authenticate with Infisical using AWS Auth as they handle the authentication process including the signed `GetCallerIdentity` query construction for you.
</Tip>
<Note>
Each identity access token has a time-to-live (TLL) which you can infer from the response of the login operation;
the default TTL is `7200` seconds which can be adjusted.
If an identity access token expires, it can no longer authenticate with the Infisical API. In this case,
a new access token should be obtained by performing another login operation.
</Note>
</Step>
</Steps>

View File

@ -7,7 +7,7 @@ description: "Learn how to use Machine Identities to programmatically interact w
An Infisical machine identity is an entity that represents a workload or application that require access to various resources in Infisical. This is conceptually similar to an IAM user in AWS or service account in Google Cloud Platform (GCP).
Each identity must authenticate with the API using a supported authentication method like [Universal Auth](/documentation/platform/identities/universal-auth) to get back a short-lived access token to be used in subsequent requests.
Each identity must authenticate using a supported authentication method like [Universal Auth](/documentation/platform/identities/universal-auth) or [AWS Auth](/documentation/platform/identities/aws-auth) to get back a short-lived access token to be used in subsequent requests.
![organization identities](/images/platform/organization/organization-machine-identities.png)
@ -21,7 +21,7 @@ Key Features:
A typical workflow for using identities consists of four steps:
1. Creating the identity with a name and [role](/documentation/platform/role-based-access-controls) in Organization Access Control > Machine Identities.
This step also involves configuring an authentication method for it such as [Universal Auth](/documentation/platform/identities/universal-auth).
This step also involves configuring an authentication method for it such as [Universal Auth](/documentation/platform/identities/universal-auth) or [AWS Auth](/documentation/platform/identities/aws-auth).
2. Adding the identity to the project(s) you want it to have access to.
3. Authenticating the identity with the Infisical API based on the configured authentication method on it and receiving a short-lived access token back.
4. Authenticating subsequent requests with the Infisical API using the short-lived access token.
@ -37,7 +37,8 @@ Machine Identity support for the rest of the clients is planned to be released i
To interact with various resources in Infisical, Machine Identities are able to authenticate using:
- [Universal Auth](/documentation/platform/identities/universal-auth): the most versatile authentication method that can be configured on an identity from any platform/environment to access Infisical.
- [Universal Auth](/documentation/platform/identities/universal-auth): A platform-agnostic authentication method that can be configured on an identity suitable to authenticate from any platform/environment.
- [AWS Auth](/documentation/platform/identities/aws-auth): An AWS-native authentication method for IAM principals like EC2 instances or Lambda functions to authenticate with Infisical.
## FAQ

View File

@ -3,17 +3,14 @@ title: Universal Auth
description: "Learn how to authenticate to Infisical from any platform or environment."
---
**Universal Auth** is the most versatile authentication method that can be configured for a [machine identity](/documentation/platform/identities/machine-identities) to access Infisical from any platform or environment.
**Universal Auth** is a platform-agnostic authentication method that can be configured for a [machine identity](/documentation/platform/identities/machine-identities) suitable to authenticate from any platform/environment.
In this method, each identity is given a **Client ID** for which you can generate one or more **Client Secret(s)**. Together, a **Client ID** and **Client Secret** can be exchanged for an access token to authenticate with the Infisical API.
## Concept
## Properties
In this method, Infisical authenticates an identity by verifying the credentials issued for it at the `/api/v1/auth/universal-auth/login` endpoint. If successful,
then Infisical returns a short-lived access token that can be used to make authenticated requests to the Infisical API.
Universal Auth supports many settings that can be beneficial for tightening your workflow security configuration:
- Support for restrictions on the number of times that the **Client Secret(s)** and access token(s) can be used.
- Support for expiration, so, if specified, the **Client Secret** of the identity will automatically be defunct after a period of time.
- Support for IP allowlisting; this means you can restrict the usage of **Client Secret(s)** and access token to a specific IP or CIDR range.
In Universal Auth, an identity is given a **Client ID** and one or more **Client Secret(s)**. Together, a **Client ID** and **Client Secret** can be exchanged for a short-lived access token to authenticate with the Infisical API.
## Workflow
@ -27,18 +24,18 @@ using the Universal Auth authentication method.
![identities organization](/images/platform/identities/identities-org.png)
When creating an identity, you specify an organization level [role](/documentation/platform/role-based-access-controls) for it to assume; you can configure roles in Organization Settings > Access Control > Organization Roles.
![identities organization create](/images/platform/identities/identities-org-create.png)
Now input a few details for your new identity. Here's some guidance for each field:
- Name (required): A friendly name for the identity.
- Role (required): A role from the **Organization Roles** tab for the identity to assume. The organization role assigned will determine what organization level resources this identity can have access to.
Once you've created an identity, you'll be prompted to configure the **Universal Auth** authentication method for it.
![identities organization create auth method](/images/platform/identities/identities-org-create-auth-method.png)
Here's some more guidance on each field:
- Access Token TTL (default is `2592000` equivalent to 30 days): The lifetime for an acccess token in seconds. This value will be referenced at renewal time.
@ -78,8 +75,9 @@ using the Universal Auth authentication method.
Next, select the identity you want to add to the project and the project level role you want to allow it to assume. The project role assigned will determine what project level resources this identity can have access to.
![identities project](/images/platform/identities/identities-project.png)
![identities project create](/images/platform/identities/identities-project-create.png)
</Step>
<Step title="Accessing the Infisical API with the identity">
To access the Infisical API as the identity, you should first perform a login operation
@ -88,16 +86,16 @@ using the Universal Auth authentication method.
#### Sample request
```
```bash Request
curl --location --request POST 'https://app.infisical.com/api/v1/auth/universal-auth/login' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'clientSecret=...' \
--data-urlencode 'clientId=...'
--data-urlencode 'clientId=...' \
--data-urlencode 'clientSecret=...'
```
#### Sample response
```
```bash Response
{
"accessToken": "...",
"expiresIn": 7200,
@ -107,7 +105,7 @@ using the Universal Auth authentication method.
```
Next, you can use the access token to authenticate with the [Infisical API](/api-reference/overview/introduction)
<Note>
Each identity access token has a time-to-live (TLL) which you can infer from the response of the login operation;
the default TTL is `7200` seconds which can be adjusted.
@ -115,6 +113,7 @@ using the Universal Auth authentication method.
If an identity access token expires, it can no longer authenticate with the Infisical API. In this case,
a new access token should be obtained by performing another login operation.
</Note>
</Step>
</Steps>
@ -134,7 +133,8 @@ using the Universal Auth authentication method.
In certain cases, you may want to extend the lifespan of an access token; to do so, you must set a max TTL parameter.
A token can be renewed any number of time and each call to renew it will extend the toke life by increments of access token TTL.
Regardless of how frequently an access token is renewed, its lifespan remains bound to the maximum TTL determined at its creation
A token can be renewed any number of time and each call to renew it will extend the toke life by increments of access token TTL.
Regardless of how frequently an access token is renewed, its lifespan remains bound to the maximum TTL determined at its creation
</Accordion>
</AccordionGroup>
</AccordionGroup>

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 KiB

View File

@ -77,6 +77,8 @@ spec:
projectSlug: <project-slug>
envSlug: <env-slug> # "dev", "staging", "prod", etc..
secretsPath: "<secrets-path>" # Root is "/"
recursive: true # Fetch all secrets from the specified path and all sub-directories. Default is false.
credentialsRef:
secretName: universal-auth-credentials
secretNamespace: default
@ -89,6 +91,7 @@ spec:
secretsScope:
envSlug: <env-slug>
secretsPath: <secrets-path> # Root is "/"
recursive: true # Fetch all secrets from the specified path and all sub-directories. Default is false.
managedSecretReference:
secretName: managed-secret

View File

@ -153,6 +153,7 @@
"documentation/platform/auth-methods/email-password",
"documentation/platform/token",
"documentation/platform/identities/universal-auth",
"documentation/platform/identities/aws-auth",
"documentation/platform/mfa",
{
"group": "SSO",
@ -211,9 +212,7 @@
},
{
"group": "Reference architectures",
"pages": [
"self-hosting/reference-architectures/aws-ecs"
]
"pages": ["self-hosting/reference-architectures/aws-ecs"]
},
"self-hosting/ee",
"self-hosting/faq"

View File

@ -31,6 +31,7 @@ export const SecretPathInput = ({
const [inputValue, setInputValue] = useState(propValue ?? "");
const [secretPath, setSecretPath] = useState("/");
const [suggestions, setSuggestions] = useState<string[]>([]);
const [isInputFocused, setIsInputFocus] = useState(false);
const [highlightedIndex, setHighlightedIndex] = useState(-1);
const debouncedInputValue = useDebounce(inputValue, 200);
@ -55,7 +56,9 @@ export const SecretPathInput = ({
) {
setSecretPath(debouncedInputValue);
}
}, [debouncedInputValue]);
useEffect(() => {
// filter suggestions based on matching
const searchFragment = debouncedInputValue.split("/").pop() || "";
const filteredSuggestions = folders
@ -65,7 +68,7 @@ export const SecretPathInput = ({
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
setSuggestions(filteredSuggestions);
}, [debouncedInputValue]);
}, [debouncedInputValue, folders]);
const handleSuggestionSelect = (selectedIndex: number) => {
if (!suggestions[selectedIndex]) {
@ -75,7 +78,7 @@ export const SecretPathInput = ({
const validPaths = inputValue.split("/");
validPaths.pop();
const newValue = `${validPaths.join("/")}/${suggestions[selectedIndex]}`;
const newValue = `${validPaths.join("/")}/${suggestions[selectedIndex]}/`;
onChange?.(newValue);
setInputValue(newValue);
setSecretPath(newValue);
@ -108,7 +111,7 @@ export const SecretPathInput = ({
return (
<Popover.Root
open={suggestions.length > 0 && inputValue.length > 1}
open={suggestions.length > 0 && isInputFocused}
onOpenChange={() => {
setHighlightedIndex(-1);
}}
@ -119,6 +122,8 @@ export const SecretPathInput = ({
type="text"
autoComplete="off"
onKeyDown={handleKeyDown}
onFocus={() => setIsInputFocus(true)}
onBlur={() => setIsInputFocus(false)}
value={inputValue}
onChange={handleInputChange}
className={containerClassName}
@ -150,8 +155,9 @@ export const SecretPathInput = ({
key={`secret-reference-secret-${i + 1}`}
>
<div
className={`${highlightedIndex === i ? "bg-gray-600" : ""
} text-md relative mb-0.5 flex w-full cursor-pointer select-none items-center justify-between rounded-md px-2 py-1 outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-500`}
className={`${
highlightedIndex === i ? "bg-gray-600" : ""
} text-md relative mb-0.5 flex w-full cursor-pointer select-none items-center justify-between rounded-md px-2 py-1 outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-500`}
>
<div className="flex gap-2">
<div className="flex items-center text-yellow-700">

View File

@ -1,5 +1,6 @@
import { IdentityAuthMethod } from "./enums";
export const identityAuthToNameMap: { [I in IdentityAuthMethod]: string } = {
[IdentityAuthMethod.UNIVERSAL_AUTH]: "Universal Auth"
[IdentityAuthMethod.UNIVERSAL_AUTH]: "Universal Auth",
[IdentityAuthMethod.AWS_AUTH]: "AWS Auth"
};

View File

@ -1,3 +1,4 @@
export enum IdentityAuthMethod {
UNIVERSAL_AUTH = "universal-auth"
UNIVERSAL_AUTH = "universal-auth",
AWS_AUTH = "aws-auth"
}

View File

@ -1,12 +1,18 @@
export { identityAuthToNameMap } from "./constants";
export { IdentityAuthMethod } from "./enums";
export {
useAddIdentityAwsAuth,
useAddIdentityUniversalAuth,
useCreateIdentity,
useCreateIdentityUniversalAuthClientSecret,
useDeleteIdentity,
useRevokeIdentityUniversalAuthClientSecret,
useUpdateIdentity,
useUpdateIdentityAwsAuth,
useUpdateIdentityUniversalAuth
} from "./mutations";
export { useGetIdentityUniversalAuth, useGetIdentityUniversalAuthClientSecrets } from "./queries";
export {
useGetIdentityAwsAuth,
useGetIdentityUniversalAuth,
useGetIdentityUniversalAuthClientSecrets
} from "./queries";

View File

@ -5,6 +5,7 @@ import { apiRequest } from "@app/config/request";
import { organizationKeys } from "../organization/queries";
import { identitiesKeys } from "./queries";
import {
AddIdentityAwsAuthDTO,
AddIdentityUniversalAuthDTO,
ClientSecretData,
CreateIdentityDTO,
@ -13,7 +14,9 @@ import {
DeleteIdentityDTO,
DeleteIdentityUniversalAuthClientSecretDTO,
Identity,
IdentityAwsAuth,
IdentityUniversalAuth,
UpdateIdentityAwsAuthDTO,
UpdateIdentityDTO,
UpdateIdentityUniversalAuthDTO
} from "./types";
@ -169,3 +172,74 @@ export const useRevokeIdentityUniversalAuthClientSecret = () => {
}
});
};
export const useAddIdentityAwsAuth = () => {
const queryClient = useQueryClient();
return useMutation<IdentityAwsAuth, {}, AddIdentityAwsAuthDTO>({
mutationFn: async ({
identityId,
stsEndpoint,
allowedPrincipalArns,
allowedAccountIds,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}) => {
const {
data: { identityAwsAuth }
} = await apiRequest.post<{ identityAwsAuth: IdentityAwsAuth }>(
`/api/v1/auth/aws-auth/identities/${identityId}`,
{
stsEndpoint,
allowedPrincipalArns,
allowedAccountIds,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}
);
return identityAwsAuth;
},
onSuccess: (_, { organizationId }) => {
queryClient.invalidateQueries(organizationKeys.getOrgIdentityMemberships(organizationId));
}
});
};
export const useUpdateIdentityAwsAuth = () => {
const queryClient = useQueryClient();
return useMutation<IdentityAwsAuth, {}, UpdateIdentityAwsAuthDTO>({
mutationFn: async ({
identityId,
stsEndpoint,
allowedPrincipalArns,
allowedAccountIds,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}) => {
const {
data: { identityAwsAuth }
} = await apiRequest.patch<{ identityAwsAuth: IdentityAwsAuth }>(
`/api/v1/auth/aws-auth/identities/${identityId}`,
{
stsEndpoint,
allowedPrincipalArns,
allowedAccountIds,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}
);
return identityAwsAuth;
},
onSuccess: (_, { organizationId }) => {
queryClient.invalidateQueries(organizationKeys.getOrgIdentityMemberships(organizationId));
}
});
};

View File

@ -2,27 +2,26 @@ import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { ClientSecretData, IdentityUniversalAuth } from "./types";
import { ClientSecretData, IdentityAwsAuth, IdentityUniversalAuth } from "./types";
export const identitiesKeys = {
getIdentityUniversalAuth: (identityId: string) =>
[{ identityId }, "identity-universal-auth"] as const,
getIdentityUniversalAuthClientSecrets: (identityId: string) =>
[{ identityId }, "identity-universal-auth-client-secrets"] as const
[{ identityId }, "identity-universal-auth-client-secrets"] as const,
getIdentityAwsAuth: (identityId: string) => [{ identityId }, "identity-aws-auth"] as const
};
export const useGetIdentityUniversalAuth = (identityId: string) => {
return useQuery({
enabled: Boolean(identityId),
queryKey: identitiesKeys.getIdentityUniversalAuth(identityId),
queryFn: async () => {
if (identityId === "") throw new Error("Identity ID is required");
const {
data: { identityUniversalAuth }
} = await apiRequest.get<{ identityUniversalAuth: IdentityUniversalAuth }>(
`/api/v1/auth/universal-auth/identities/${identityId}`
);
return identityUniversalAuth;
}
});
@ -30,17 +29,30 @@ export const useGetIdentityUniversalAuth = (identityId: string) => {
export const useGetIdentityUniversalAuthClientSecrets = (identityId: string) => {
return useQuery({
enabled: Boolean(identityId),
queryKey: identitiesKeys.getIdentityUniversalAuthClientSecrets(identityId),
queryFn: async () => {
if (identityId === "") return [];
const {
data: { clientSecretData }
} = await apiRequest.get<{ clientSecretData: ClientSecretData[] }>(
`/api/v1/auth/universal-auth/identities/${identityId}/client-secrets`
);
return clientSecretData;
}
});
};
export const useGetIdentityAwsAuth = (identityId: string) => {
return useQuery({
enabled: Boolean(identityId),
queryKey: identitiesKeys.getIdentityAwsAuth(identityId),
queryFn: async () => {
const {
data: { identityAwsAuth }
} = await apiRequest.get<{ identityAwsAuth: IdentityAwsAuth }>(
`/api/v1/auth/aws-auth/identities/${identityId}`
);
return identityAwsAuth;
}
});
};

View File

@ -38,19 +38,19 @@ export type IdentityMembership = {
customRoleSlug: string;
} & (
| {
isTemporary: false;
temporaryRange: null;
temporaryMode: null;
temporaryAccessEndTime: null;
temporaryAccessStartTime: null;
}
isTemporary: false;
temporaryRange: null;
temporaryMode: null;
temporaryAccessEndTime: null;
temporaryAccessStartTime: null;
}
| {
isTemporary: true;
temporaryRange: string;
temporaryMode: string;
temporaryAccessEndTime: string;
temporaryAccessStartTime: string;
}
isTemporary: true;
temporaryRange: string;
temporaryMode: string;
temporaryAccessEndTime: string;
temporaryAccessStartTime: string;
}
)
>;
createdAt: string;
@ -113,6 +113,46 @@ export type UpdateIdentityUniversalAuthDTO = {
}[];
};
export type IdentityAwsAuth = {
identityId: string;
type: "iam";
stsEndpoint: string;
allowedPrincipalArns: string;
allowedAccountIds: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: IdentityTrustedIp[];
};
export type AddIdentityAwsAuthDTO = {
organizationId: string;
identityId: string;
stsEndpoint: string;
allowedPrincipalArns: string;
allowedAccountIds: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: {
ipAddress: string;
}[];
};
export type UpdateIdentityAwsAuthDTO = {
organizationId: string;
identityId: string;
stsEndpoint?: string;
allowedPrincipalArns?: string;
allowedAccountIds?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: {
ipAddress: string;
}[];
};
export type CreateIdentityUniversalAuthClientSecretDTO = {
identityId: string;
description?: string;

View File

@ -79,7 +79,7 @@ export const useGetFoldersByEnv = ({
});
});
return [...names];
}, [(folders || []).map((folder) => folder.data)]);
}, [...(folders || []).map((folder) => folder.data)]);
const isFolderPresentInEnv = useCallback(
(name: string, env: string) => {
@ -91,7 +91,7 @@ export const useGetFoldersByEnv = ({
}
return false;
},
[(folders || []).map((folder) => folder.data)]
[...(folders || []).map((folder) => folder.data)]
);
const getFolderByNameAndEnv = useCallback(

View File

@ -1,3 +1,4 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
@ -13,6 +14,7 @@ import {
import { IdentityAuthMethod } from "@app/hooks/api/identities";
import { UsePopUpState } from "@app/hooks/usePopUp";
import { IdentityAwsAuthForm } from "./IdentityAwsAuthForm";
import { IdentityUniversalAuthForm } from "./IdentityUniversalAuthForm";
type Props = {
@ -24,22 +26,25 @@ type Props = {
) => void;
};
const identityAuthMethods = [{ label: "Universal Auth", value: IdentityAuthMethod.UNIVERSAL_AUTH }];
const identityAuthMethods = [
{ label: "Universal Auth", value: IdentityAuthMethod.UNIVERSAL_AUTH },
{ label: "AWS Auth", value: IdentityAuthMethod.AWS_AUTH }
];
const schema = yup
.object({
authMethod: yup.string().required("Auth method is required") // TODO: better enforcement here
authMethod: yup.string().required("Auth method is required")
})
.required();
export type FormData = yup.InferType<typeof schema>;
export const IdentityAuthMethodModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Props) => {
const {
control
// watch,
} = useForm<FormData>({
resolver: yupResolver(schema)
const { control, watch, setValue } = useForm<FormData>({
resolver: yupResolver(schema),
defaultValues: {
authMethod: IdentityAuthMethod.UNIVERSAL_AUTH
}
});
const identityAuthMethodData = popUp?.identityAuthMethod?.data as {
@ -48,16 +53,41 @@ export const IdentityAuthMethodModal = ({ popUp, handlePopUpOpen, handlePopUpTog
authMethod?: IdentityAuthMethod;
};
// const authMethod = watch("authMethod");
useEffect(() => {
if (identityAuthMethodData?.authMethod) {
setValue("authMethod", identityAuthMethodData.authMethod);
return;
}
setValue("authMethod", IdentityAuthMethod.UNIVERSAL_AUTH);
}, [identityAuthMethodData?.authMethod]);
const authMethod = watch("authMethod");
const renderIdentityAuthForm = () => {
return (
<IdentityUniversalAuthForm
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
identityAuthMethodData={identityAuthMethodData}
/>
);
switch (identityAuthMethodData?.authMethod ?? authMethod) {
case IdentityAuthMethod.AWS_AUTH: {
return (
<IdentityAwsAuthForm
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
identityAuthMethodData={identityAuthMethodData}
/>
);
}
case IdentityAuthMethod.UNIVERSAL_AUTH: {
return (
<IdentityUniversalAuthForm
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
identityAuthMethodData={identityAuthMethodData}
/>
);
}
default: {
return <div />;
}
}
};
return (
@ -83,6 +113,7 @@ export const IdentityAuthMethodModal = ({ popUp, handlePopUpOpen, handlePopUpTog
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
isDisabled={!!identityAuthMethodData?.authMethod}
>
{identityAuthMethods.map(({ label, value }) => (
<SelectItem value={String(value || "")} key={label}>

View File

@ -0,0 +1,352 @@
import { useEffect } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, IconButton, Input } from "@app/components/v2";
import { useOrganization, useSubscription } from "@app/context";
import {
useAddIdentityAwsAuth,
useGetIdentityAwsAuth,
useUpdateIdentityAwsAuth
} from "@app/hooks/api";
import { IdentityAuthMethod } from "@app/hooks/api/identities";
import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
const schema = yup
.object({
stsEndpoint: yup.string(),
allowedPrincipalArns: yup.string(),
allowedAccountIds: yup.string(),
accessTokenTTL: yup.string().required("Access Token TTL is required"),
accessTokenMaxTTL: yup.string().required("Access Max Token TTL is required"),
accessTokenNumUsesLimit: yup.string().required("Access Token Max Number of Uses is required"),
accessTokenTrustedIps: yup
.array(
yup.object({
ipAddress: yup.string().max(50).required().label("IP Address")
})
)
.min(1)
.required()
.label("Access Token Trusted IP")
})
.required();
export type FormData = yup.InferType<typeof schema>;
type Props = {
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
handlePopUpToggle: (
popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
state?: boolean
) => void;
identityAuthMethodData: {
identityId: string;
name: string;
authMethod?: IdentityAuthMethod;
};
};
export const IdentityAwsAuthForm = ({
handlePopUpOpen,
handlePopUpToggle,
identityAuthMethodData
}: Props) => {
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { subscription } = useSubscription();
const { mutateAsync: addMutateAsync } = useAddIdentityAwsAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityAwsAuth();
const { data } = useGetIdentityAwsAuth(identityAuthMethodData?.identityId ?? "");
const {
control,
handleSubmit,
reset,
formState: { isSubmitting }
} = useForm<FormData>({
resolver: yupResolver(schema),
defaultValues: {
stsEndpoint: "https://sts.amazonaws.com/",
allowedPrincipalArns: "",
allowedAccountIds: "",
accessTokenTTL: "2592000",
accessTokenMaxTTL: "2592000",
accessTokenNumUsesLimit: "0",
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]
}
});
const {
fields: accessTokenTrustedIpsFields,
append: appendAccessTokenTrustedIp,
remove: removeAccessTokenTrustedIp
} = useFieldArray({ control, name: "accessTokenTrustedIps" });
useEffect(() => {
if (data) {
reset({
stsEndpoint: data.stsEndpoint,
allowedPrincipalArns: data.allowedPrincipalArns,
allowedAccountIds: data.allowedAccountIds,
accessTokenTTL: String(data.accessTokenTTL),
accessTokenMaxTTL: String(data.accessTokenMaxTTL),
accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit),
accessTokenTrustedIps: data.accessTokenTrustedIps.map(
({ ipAddress, prefix }: IdentityTrustedIp) => {
return {
ipAddress: `${ipAddress}${prefix !== undefined ? `/${prefix}` : ""}`
};
}
)
});
} else {
reset({
stsEndpoint: "https://sts.amazonaws.com/",
allowedPrincipalArns: "",
allowedAccountIds: "",
accessTokenTTL: "2592000",
accessTokenMaxTTL: "2592000",
accessTokenNumUsesLimit: "0",
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]
});
}
}, [data]);
const onFormSubmit = async ({
allowedPrincipalArns,
allowedAccountIds,
stsEndpoint,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}: FormData) => {
try {
if (!identityAuthMethodData) return;
if (data) {
await updateMutateAsync({
organizationId: orgId,
stsEndpoint,
allowedPrincipalArns,
allowedAccountIds,
identityId: identityAuthMethodData.identityId,
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
accessTokenTrustedIps
});
} else {
await addMutateAsync({
organizationId: orgId,
identityId: identityAuthMethodData.identityId,
stsEndpoint: stsEndpoint || "",
allowedPrincipalArns: allowedPrincipalArns || "",
allowedAccountIds: allowedAccountIds || "",
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
accessTokenTrustedIps
});
}
handlePopUpToggle("identityAuthMethod", false);
createNotification({
text: `Successfully ${
identityAuthMethodData?.authMethod ? "updated" : "configured"
} auth method`,
type: "success"
});
reset();
} catch (err) {
createNotification({
text: `Failed to ${identityAuthMethodData?.authMethod ? "update" : "configure"} identity`,
type: "error"
});
}
};
return (
<form onSubmit={handleSubmit(onFormSubmit)}>
<Controller
control={control}
defaultValue="2592000"
name="allowedPrincipalArns"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Allowed Principal ARNs"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
placeholder="arn:aws:iam::123456789012:role/MyRoleName, arn:aws:iam::123456789012:user/MyUserName..."
type="text"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="allowedAccountIds"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Allowed Account IDs"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="123456789012, ..." />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="https://sts.amazonaws.com/"
name="stsEndpoint"
render={({ field, fieldState: { error } }) => (
<FormControl label="STS Endpoint" isError={Boolean(error)} errorText={error?.message}>
<Input {...field} placeholder="https://sts.amazonaws.com/" type="text" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenMaxTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="1" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
{accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller
control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`}
defaultValue="0.0.0.0/0"
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
label={index === 0 ? "Access Token Trusted IPs" : undefined}
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan");
}}
placeholder="123.456.789.0"
/>
</FormControl>
);
}}
/>
<IconButton
onClick={() => {
if (subscription?.ipAllowlisting) {
removeAccessTokenTrustedIp(index);
return;
}
handlePopUpOpen("upgradePlan");
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="p-3"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</div>
))}
<div className="my-4 ml-1">
<Button
variant="outline_bg"
onClick={() => {
if (subscription?.ipAllowlisting) {
appendAccessTokenTrustedIp({
ipAddress: "0.0.0.0/0"
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
>
Add IP Address
</Button>
</div>
<div className="flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
{identityAuthMethodData?.authMethod ? "Update" : "Configure"}
</Button>
<Button
colorSchema="secondary"
variant="plain"
onClick={() => handlePopUpToggle("identityAuthMethod", false)}
>
{identityAuthMethodData?.authMethod ? "Cancel" : "Skip"}
</Button>
</div>
</form>
);
};

View File

@ -15,7 +15,10 @@ import {
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import { useCreateIdentity, useGetOrgRoles, useUpdateIdentity } from "@app/hooks/api";
import { IdentityAuthMethod, useAddIdentityUniversalAuth } from "@app/hooks/api/identities";
import {
IdentityAuthMethod
// useAddIdentityUniversalAuth
} from "@app/hooks/api/identities";
import { UsePopUpState } from "@app/hooks/usePopUp";
const schema = yup
@ -40,9 +43,7 @@ type Props = {
handlePopUpToggle: (popUpName: keyof UsePopUpState<["identity"]>, state?: boolean) => void;
};
export const IdentityModal = ({ popUp, /* handlePopUpOpen, */ handlePopUpToggle }: Props) => {
export const IdentityModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Props) => {
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
@ -50,7 +51,7 @@ export const IdentityModal = ({ popUp, /* handlePopUpOpen, */ handlePopUpToggle
const { mutateAsync: createMutateAsync } = useCreateIdentity();
const { mutateAsync: updateMutateAsync } = useUpdateIdentity();
const { mutateAsync: addMutateAsync } = useAddIdentityUniversalAuth();
// const { mutateAsync: addMutateAsync } = useAddIdentityUniversalAuth();
const {
control,
@ -113,31 +114,31 @@ export const IdentityModal = ({ popUp, /* handlePopUpOpen, */ handlePopUpToggle
// create
const {
id: createdId
// name: createdName,
// authMethod
id: createdId,
name: createdName,
authMethod
} = await createMutateAsync({
name,
role: role || undefined,
organizationId: orgId
});
await addMutateAsync({
organizationId: orgId,
identityId: createdId,
clientSecretTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }],
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }],
accessTokenTTL: 2592000,
accessTokenMaxTTL: 2592000,
accessTokenNumUsesLimit: 0
});
// await addMutateAsync({
// organizationId: orgId,
// identityId: createdId,
// clientSecretTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }],
// accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }],
// accessTokenTTL: 2592000,
// accessTokenMaxTTL: 2592000,
// accessTokenNumUsesLimit: 0
// });
handlePopUpToggle("identity", false);
// handlePopUpOpen("identityAuthMethod", {
// identityId: createdId,
// name: createdName,
// authMethod
// });
handlePopUpOpen("identityAuthMethod", {
identityId: createdId,
name: createdName,
authMethod
});
}
createNotification({

View File

@ -23,8 +23,6 @@ import { useGetIdentityMembershipOrgs, useGetOrgRoles, useUpdateIdentity } from
import { IdentityAuthMethod, identityAuthToNameMap } from "@app/hooks/api/identities";
import { UsePopUpState } from "@app/hooks/usePopUp";
// TODO: some kind of map
type Props = {
handlePopUpOpen: (
popUpName: keyof UsePopUpState<
@ -44,7 +42,6 @@ type Props = {
};
export const IdentityTable = ({ handlePopUpOpen }: Props) => {
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";

View File

@ -63,7 +63,6 @@ export const IdentityUniversalAuthForm = ({
handlePopUpToggle,
identityAuthMethodData
}: Props) => {
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { subscription } = useSubscription();
@ -384,7 +383,7 @@ export const IdentityUniversalAuthForm = ({
variant="plain"
onClick={() => handlePopUpToggle("identityAuthMethod", false)}
>
Cancel
{identityAuthMethodData?.authMethod ? "Cancel" : "Skip"}
</Button>
</div>
</form>

View File

@ -13,7 +13,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: v0.5.0
version: v0.5.1
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.

View File

@ -67,6 +67,8 @@ spec:
properties:
envSlug:
type: string
recursive:
type: boolean
secretsPath:
type: string
required:
@ -111,6 +113,8 @@ spec:
type: string
projectSlug:
type: string
recursive:
type: boolean
secretsPath:
type: string
required:

View File

@ -32,7 +32,7 @@ controllerManager:
- ALL
image:
repository: infisical/kubernetes-operator
tag: v0.5.0 # fixed to prevent accidental upgrade
tag: v0.5.1 # fixed to prevent accidental upgrade
resources:
limits:
cpu: 500m

View File

@ -38,6 +38,8 @@ type SecretScopeInWorkspace struct {
SecretsPath string `json:"secretsPath"`
// +kubebuilder:validation:Required
EnvSlug string `json:"envSlug"`
// +kubebuilder:validation:Optional
Recursive bool `json:"recursive"`
}
type MachineIdentityScopeInWorkspace struct {
@ -47,6 +49,8 @@ type MachineIdentityScopeInWorkspace struct {
EnvSlug string `json:"envSlug"`
// +kubebuilder:validation:Required
ProjectSlug string `json:"projectSlug"`
// +kubebuilder:validation:Optional
Recursive bool `json:"recursive"`
}
type KubeSecretReference struct {

View File

@ -67,6 +67,8 @@ spec:
properties:
envSlug:
type: string
recursive:
type: boolean
secretsPath:
type: string
required:
@ -111,6 +113,8 @@ spec:
type: string
projectSlug:
type: string
recursive:
type: boolean
secretsPath:
type: string
required:

View File

@ -19,12 +19,14 @@ spec:
secretsScope:
envSlug: <env-slug>
secretsPath: <secrets-path> # Root is "/"
recursive: true # Wether or not to use recursive mode (Fetches all secrets in an environment from a given secret path, and all folders inside the path) / defaults to false
universalAuth:
secretsScope:
projectSlug: <project-slug>
envSlug: <env-slug> # "dev", "staging", "prod", etc..
secretsPath: "<secrets-path>" # Root is "/"
recursive: true # Wether or not to use recursive mode (Fetches all secrets in an environment from a given secret path, and all folders inside the path) / defaults to false
credentialsRef:
secretName: universal-auth-credentials

View File

@ -269,7 +269,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
} else if infisicalMachineIdentityCreds.ClientId != "" && infisicalMachineIdentityCreds.ClientSecret != "" {
authStrategy = AuthStrategy.UNIVERSAL_MACHINE_IDENTITY
} else {
return fmt.Errorf("no authentication method provided. You must provide either a valid service token or a service account details to fetch secrets")
return fmt.Errorf("no authentication method provided. You must provide either a valid service token or a service account details to fetch secrets\n")
}
r.SetInfisicalTokenLoadCondition(ctx, &infisicalSecret, err)
@ -312,8 +312,9 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
} else if authStrategy == AuthStrategy.SERVICE_TOKEN { // Service Tokens (deprecated)
envSlug := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.EnvSlug
secretsPath := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.SecretsPath
recursive := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.Recursive
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceToken(infisicalToken, secretVersionBasedOnETag, envSlug, secretsPath)
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceToken(infisicalToken, secretVersionBasedOnETag, envSlug, secretsPath, recursive)
if err != nil {
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
}

View File

@ -66,6 +66,10 @@ func CallGetSecretsV3(httpClient *resty.Client, request GetEncryptedSecretsV3Req
httpRequest.SetQueryParam("secretPath", request.SecretPath)
}
if request.Recursive {
httpRequest.SetQueryParam("recursive", "true")
}
response, err := httpRequest.Get(fmt.Sprintf("%v/v3/secrets", API_HOST_URL))
if err != nil {
@ -148,19 +152,23 @@ func CallUniversalMachineIdentityRefreshAccessToken(request MachineIdentityUnive
func CallGetDecryptedSecretsV3(httpClient *resty.Client, request GetDecryptedSecretsV3Request) (GetDecryptedSecretsV3Response, error) {
var decryptedSecretsResponse GetDecryptedSecretsV3Response
response, err := httpClient.
req := httpClient.
R().
SetResult(&decryptedSecretsResponse).
SetHeader("User-Agent", USER_AGENT_NAME).
SetQueryParam("secretPath", request.SecretPath).
SetQueryParam("workspaceSlug", request.ProjectSlug).
SetQueryParam("environment", request.Environment).
Get(fmt.Sprintf("%v/v3/secrets/raw", API_HOST_URL))
SetQueryParam("environment", request.Environment)
if request.Recursive {
req.SetQueryParam("recursive", "true")
}
response, err := req.Get(fmt.Sprintf("%v/v3/secrets/raw", API_HOST_URL))
if err != nil {
return GetDecryptedSecretsV3Response{}, fmt.Errorf("CallGetDecryptedSecretsV3: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return GetDecryptedSecretsV3Response{}, fmt.Errorf("CallGetDecryptedSecretsV3: Unsuccessful response: [response=%s]", response)
}

View File

@ -31,6 +31,7 @@ type GetEncryptedWorkspaceKeyResponse struct {
type GetEncryptedSecretsV3Request struct {
Environment string `json:"environment"`
WorkspaceId string `json:"workspaceId"`
Recursive bool `json:"recursive"`
SecretPath string `json:"secretPath"`
IncludeImport bool `json:"include_imports"`
ETag string `json:"etag,omitempty"`
@ -100,6 +101,7 @@ type GetDecryptedSecretsV3Request struct {
ProjectSlug string `json:"workspaceSlug"`
Environment string `json:"environment"`
SecretPath string `json:"secretPath"`
Recursive bool `json:"recursive"`
ETag string `json:"etag,omitempty"`
}

View File

@ -60,6 +60,7 @@ func GetPlainTextSecretsViaUniversalAuth(accessToken string, etag string, secret
secretsResponse, err := api.CallGetDecryptedSecretsV3(httpClient, api.GetDecryptedSecretsV3Request{
ProjectSlug: secretScope.ProjectSlug,
Environment: secretScope.EnvSlug,
Recursive: secretScope.Recursive,
SecretPath: secretScope.SecretsPath,
ETag: etag,
})
@ -85,7 +86,7 @@ func GetPlainTextSecretsViaUniversalAuth(accessToken string, etag string, secret
}, nil
}
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, envSlug string, secretPath string) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) {
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, envSlug string, secretPath string, recursive bool) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) {
serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
if len(serviceTokenParts) < 4 {
return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
@ -106,6 +107,7 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, en
encryptedSecretsResponse, err := api.CallGetSecretsV3(httpClient, api.GetEncryptedSecretsV3Request{
WorkspaceId: serviceTokenDetails.Workspace,
Environment: envSlug,
Recursive: recursive,
ETag: etag,
SecretPath: secretPath,
})
@ -376,7 +378,7 @@ func ExpandSecrets(secrets []model.SingleEnvironmentVariable, infisicalToken str
if crossRefSec, ok := crossEnvRefSecs[uniqKey]; !ok {
// if not in cross reference cache, fetch it from server
refSecs, _, err := GetPlainTextSecretsViaServiceToken(infisicalToken, "", env, secPath)
refSecs, _, err := GetPlainTextSecretsViaServiceToken(infisicalToken, "", env, secPath, false)
if err != nil {
fmt.Printf("Could not fetch secrets in environment: %s secret-path: %s", env, secPath)
// HandleError(err, fmt.Sprintf("Could not fetch secrets in environment: %s secret-path: %s", env, secPath), "If you are using a service token to fetch secrets, please ensure it is valid")