1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-03-23 03:03:05 +00:00

Compare commits

..

131 Commits

Author SHA1 Message Date
4a153e5658 Merge pull request from akhilmhdh/fix/sec-interpolation-undefined
fix(secret-reference): fixed undefined if value not found
2024-01-10 09:59:09 -05:00
7324822be5 fix(secret-reference): fixed undefined if value not found 2024-01-10 11:45:46 +05:30
766f301aea patch agent config by env 2024-01-09 14:30:29 -05:00
b815e3eb56 Merge pull request from Infisical/daniel/fix-sdk-contribution-image
(Fix): Image in SDK contribution guide not loading
2024-01-08 14:56:27 -05:00
31231cfcca Update developing.mdx 2024-01-08 23:30:10 +04:00
ee772e4a77 allow reading universal auth creds from env in agent 2024-01-07 17:00:42 -05:00
7bc29c5981 Merge pull request from Infisical/query-by-secret-version
Add version query param to GET secret raw and regular endpoints
2024-01-07 16:07:49 -05:00
e9a89930da Merge pull request from Infisical/multi-integration-auth
Enable new integration auth credential for each new integration
2024-01-07 14:49:04 -05:00
b85499859c Merge pull request from Infisical/identities-ipv6
Add IPv6 consideration to default universal auth IP allowlist
2024-01-07 16:37:06 +01:00
7f17194c0f Add IPv6 consideration to default identities IP allowlist 2024-01-07 16:32:25 +01:00
1e1ad450d2 Add version query param to GET secret endpoint 2024-01-07 14:25:33 +01:00
5287b322d8 Enable new integration auth for each new integration 2024-01-07 12:49:59 +01:00
45d96be1ff added base64 support for config and templates 2024-01-06 23:43:04 -05:00
12840bfdbd add exit after auth setting 2024-01-06 17:17:21 -05:00
fef5369738 Merge pull request from Infisical/identity-apis
Update various identities items
2024-01-06 17:11:01 +01:00
c94b7d63f6 Update various identities items 2024-01-06 17:04:44 +01:00
485ddc5c50 Merge pull request from Infisical/patch-railway
Fix client-side railway integration issue
2024-01-06 16:14:16 +01:00
edd9c66e49 Remove commented print statements 2024-01-06 16:11:22 +01:00
0a3b85534b Fix client-side railway integration issue 2024-01-06 16:09:15 +01:00
ec2cc5162e Merge pull request from Infisical/daniel/sdk-contribution-guide
Contribution guide refactor & SDK contribution guide
2024-01-05 20:26:17 -05:00
7ce472957c Fixed quality 2024-01-06 04:04:09 +04:00
8529e0da3d Update developing.mdx 2024-01-06 03:41:31 +04:00
e5a5433f10 Update developing.mdx 2024-01-06 03:00:14 +04:00
ee6e518ff8 Update link to contribution guide 2024-01-06 02:58:26 +04:00
15a7222505 Update mint.json 2024-01-06 02:58:16 +04:00
25d482cc62 Create sdk-flow.png 2024-01-06 02:58:12 +04:00
785a2bec6a Added SDK guide 2024-01-06 02:58:08 +04:00
449466f326 Restructure 2024-01-06 02:58:02 +04:00
4131e9c3f1 Added getting started section 2024-01-06 02:57:53 +04:00
310595256f Restructured existing guide 2024-01-06 02:57:21 +04:00
1737880e58 Merge pull request from Infisical/snyk-fix-b96b562a611b0789d0a73c522a261f22
[Snyk] Security upgrade probot from 12.3.1 to 12.3.3
2024-01-05 11:20:43 -05:00
b72483f5f2 Merge pull request from Emiliaaah/fix-agent-secret-path
fix(cli): secret-path directive for agent
2024-01-05 10:39:39 -05:00
ee14bda706 Merge pull request from rlaisqls/error-message-typos
Fix error message typos
2024-01-05 18:18:20 +04:00
e56463d52b fix(cli): secret-path directive for agent 2024-01-05 15:05:57 +01:00
ebd3d7c7c4 Merge pull request from Infisical/fix-vercel-preview-env
Fix: Vercel integration preview environment client side error
2024-01-04 10:18:25 -05:00
9ecbfe201b Update create.tsx 2024-01-04 17:42:31 +04:00
ba2a03897f update secret import create notif 2024-01-04 01:55:34 -05:00
304f14c0ed update service token create notif 2024-01-04 01:52:03 -05:00
51e5c25e16 update imports/service token crud 2024-01-04 00:55:03 -05:00
0f6490b1e7 move cli to bin folder 2024-01-03 20:17:34 -05:00
f894e48fcb remove unused import 2024-01-02 13:55:01 -05:00
37cfa22619 add back macos build 2024-01-02 13:47:15 -05:00
94557344b7 wrap cli into a docker image 2024-01-02 13:43:55 -05:00
d5063018eb Added identities, universal auth, agent to changelog 2024-01-02 10:05:43 +01:00
51d68505d3 Merge pull request from Infisical/posthog-revamp
removed posthog cli export events
2023-12-29 15:18:59 -05:00
ade27ad072 Fix typos 2023-12-29 13:26:08 +09:00
683c512bce Merge pull request from Infisical/ui-improvements
ui and docs improvements
2023-12-25 14:33:47 -05:00
43ff28b5fb added terraform useragent 2023-12-24 17:13:29 -08:00
ce41855e84 added sdk useragent and channel 2023-12-24 16:58:48 -08:00
d24461b17c removed posthog cli export events 2023-12-24 15:49:18 -08:00
1797e56f9f fixed sdk guides 2023-12-24 13:30:59 -08:00
74f3ca5356 Merge pull request from Infisical/sdk/docs-update-2
Sdk/docs update 2
2023-12-24 21:57:52 +04:00
db27beaf0b Update overview.mdx 2023-12-24 21:54:57 +04:00
d6e55f51f2 Updated Python docs 2023-12-24 21:36:47 +04:00
e9b5996567 Updated node caching docs 2023-12-24 21:36:40 +04:00
094fe73917 Updated Java caching docs 2023-12-24 21:36:31 +04:00
dc3f85e92e Re-added an updated FAQ 2023-12-24 17:11:20 +04:00
c463256058 Updated Python docs 2023-12-24 17:11:08 +04:00
8df22302fd Updated Node docs 2023-12-24 17:11:03 +04:00
f37fa2bbf5 Updated Java docs 2023-12-24 17:10:54 +04:00
597c9d6f2a fix docs sdk errors 2023-12-23 17:17:10 -08:00
24d2eea930 ui and docs improvements 2023-12-23 16:06:00 -08:00
382cb910af tps 2023-12-23 17:31:34 -05:00
6725475575 Merge pull request from Infisical/sdk/docs-update
SDK documentation update
2023-12-23 09:30:35 -08:00
026864951b Updated links 2023-12-23 15:55:20 +04:00
287ed05ab7 Removed FAQ for now 2023-12-23 15:50:14 +04:00
37b036e614 Update overview.mdx 2023-12-23 15:49:03 +04:00
024914c168 Update python.mdx 2023-12-23 15:48:24 +04:00
19e8b6d37b Update node.mdx 2023-12-23 15:48:21 +04:00
b6d648f1f3 Added Java docs 2023-12-23 15:48:14 +04:00
a514a62a29 Fixed typos 2023-12-23 15:48:02 +04:00
2f24956651 Updated coming soon description 2023-12-23 15:47:16 +04:00
13d058025c Formatting and link changes 2023-12-23 15:29:24 +04:00
8ccaa7f29b Updated python docs 2023-12-23 15:29:17 +04:00
b83964051c Added required to required fields 2023-12-23 15:29:08 +04:00
0a2b078bdc Update node.mdx 2023-12-23 15:12:39 +04:00
40d16fa996 Updated Node.js docs 2023-12-23 15:10:30 +04:00
a3739cfe50 Update overview.mdx 2023-12-21 22:24:53 -08:00
a73623258e Update kubernetes-helm.mdx 2023-12-21 17:47:49 -08:00
6da39f41a6 Merge pull request from Infisical/restyle-self-hosting-docs
Restyle self-hosting docs for Docker / Docker Compose
2023-12-20 19:53:21 +07:00
69bbbfcfd8 Restyle self-hosting docs for Docker / Docker Compose 2023-12-20 19:52:17 +07:00
c9d58ec77d Merge pull request from Infisical/self-hosting-railway
Add self-hosting docs for Railway
2023-12-20 17:06:54 +07:00
cb364186d8 Add self-hosting docs for Railway 2023-12-20 17:05:28 +07:00
918afe05b6 Merge pull request from Infisical/self-hosting-aws-lightsail
Finish self-hosting docs for AWS Lightsail
2023-12-20 15:56:05 +07:00
e822820151 Finish self-hosting docs for AWS Lightsail 2023-12-20 15:42:02 +07:00
b5ac49eefe Merge pull request from akhilmhdh/feat/token-expire-null
fix: made expire optional on service token creation
2023-12-19 09:35:16 -05:00
b21d1a0ed2 Merge pull request from Infisical/self-hosting-azure-app-service
Add self-hosting docs for Azure App Service
2023-12-19 21:01:06 +07:00
70f1122362 Add self-hosting docs for Azure App Service 2023-12-19 20:57:08 +07:00
ea03db8a2c fix: made expire optional on service token creation 2023-12-19 15:46:03 +05:30
38d9abca17 Merge pull request from Infisical/self-hosting-azure-container-instances
Add self-hosting docs for Azure Container Instances
2023-12-19 15:21:01 +07:00
5bed2580c3 Add self-hosting docs for Azure Container Instances 2023-12-19 15:19:24 +07:00
d0b899897b Merge pull request from Infisical/add-crd-owner
add crd owner
2023-12-18 19:26:26 -05:00
1861dc85de add crd owner 2023-12-18 19:25:23 -05:00
bc6bf33674 Merge pull request from Infisical/self-hosting-gcp-cloud-run
Add docs for deploying Infisical with GCP Cloud Run
2023-12-18 16:54:00 +07:00
44fd35baf5 Add docs for deploying Infisical with GCP Cloud Run 2023-12-18 16:52:28 +07:00
8ddfee4c36 Merge pull request from Infisical/self-hosting-flyio
Add self-hosting docs for Fly.io
2023-12-18 12:11:32 +07:00
4d0bff4377 Add self-hosting docs for Fly.io 2023-12-18 12:10:18 +07:00
c7b2489d0b fix: backend/package.json & backend/package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-PROBOT-6129524
2023-12-17 14:48:29 +00:00
68eb0f8dd9 throw bad request when max uses reached 2023-12-15 15:40:20 -05:00
5941e8e836 Merge pull request from akhilmhdh/fix/secret-approval-patch
fix: secret approval loading failed for commiter on approval
2023-12-15 09:29:41 -05:00
80e50d13ec fix: secret approval loading failed for commiter on approval 2023-12-15 18:10:54 +05:30
99c8dda4e1 Merge pull request from Infisical/sso-docs
Update SSO docs to use Mintlify steps
2023-12-15 13:58:31 +07:00
14c8e3fa3b Update SSO docs to use Mintlify steps 2023-12-15 13:54:28 +07:00
7aa3cb53a2 Merge pull request from Infisical/patch-5
extract base from template source path
2023-12-14 15:19:39 -05:00
567309e848 extract base from template source path 2023-12-14 15:17:14 -05:00
f264340903 Merge pull request from Infisical/saml-org-redirect
Update redirect to org after SAML SSO
2023-12-14 23:12:31 +07:00
51b788cc5b Update redirect to org after SSO 2023-12-14 23:07:22 +07:00
8e0f424249 Merge pull request from Infisical/integrations-docs
Add Mintlify steps to integration pages
2023-12-14 12:08:39 +07:00
f3767d3963 Add Mintlify steps to integration pages 2023-12-14 11:35:50 +07:00
51cbfdbc46 update uni auth doc image paths 2023-12-13 19:32:16 -05:00
f5a580eb72 fix broken link to uni auth 2023-12-13 19:15:06 -05:00
460ebf3296 patch getDistinctId 2023-12-13 19:12:02 -05:00
7f7f11c970 Merge pull request from Infisical/patch-4
parse bot not found in agent
2023-12-13 18:25:27 -05:00
f799e224a0 use RequestError instead of Error for bot 2023-12-13 18:22:29 -05:00
8a87277fe6 parse bot not found in agent 2023-12-13 18:07:39 -05:00
32805c726a add docs for uni auth in agent 2023-12-13 17:27:30 -05:00
6c4a6d31e4 Merge pull request from Infisical/identities-docs
Update Identities Documentation + related API Reference Items
2023-12-13 16:57:13 -05:00
e7b89b645f Merge branch 'main' into identities-docs 2023-12-13 16:56:35 -05:00
b60cf2eb07 make minor updates to auth docs 2023-12-13 16:52:57 -05:00
cf5a79995f revert defaults to 30 days 2023-12-13 16:52:23 -05:00
c51f09fd3a Merge pull request from Infisical/patch-3
sync package.lock frontend
2023-12-13 14:45:48 -05:00
f9444c5205 sync package.lock frontend 2023-12-13 14:31:10 -05:00
7dd0943b2d remove sleep from template engine agent 2023-12-13 14:19:30 -05:00
31a9f032b3 Merge pull request from akhilmhdh/feat/bring-back-secret-index
feat: brought back secret indexing popup in overview page
2023-12-13 12:59:37 -05:00
8bf7eba07b fix: show popup only for admins 2023-12-13 11:55:44 +05:30
d2f959558e fix: resolved recursion issue in select 2023-12-12 22:29:38 +05:30
e50c89e326 feat: brought back secret indexing popup in overview page 2023-12-12 21:03:47 +05:30
6cda14328b Update getting started guide for fetching secrets via API 2023-12-12 17:59:56 +07:00
b551ee50e7 Fix merge conflicts 2023-12-12 15:50:14 +07:00
93aeacc6b6 Add API reference docs for identity / universal auth endpoints 2023-12-12 13:42:17 +07:00
5f29562fad Update existing endpoints in API reference to support Identities, update Identities docs 2023-12-11 20:01:32 +07:00
245 changed files with 16316 additions and 2958 deletions
.goreleaser.yamlREADME.md
backend
cli
docs
api-reference
changelog
contributing
documentation
images
getting-started/api
platform
sdk-flow.png
self-hosting/deployment-options
infisical-agent
integrations
internals
mint.json
sdks
self-hosting
spec.yaml
frontend
package-lock.jsonpackage.json
src
components/signup
hooks/api
auditLogs
integrationAuth
integrations
workspace
layouts/AppLayout
pages
integrations
org/[id]/overview
views
IntegrationsPage
Login
Login.utils.tsx
components
MFAStep
PasswordStep
Org/MembersPage/components
OrgIdentityTab/components/IdentitySection
OrgMembersTab/components/OrgMembersSection
Project/MembersPage/components
IdentityTab/components/IdentitySection
MemberListTab
ServiceTokenTab/components/ServiceTokenSection
SecretApprovalPage
SecretMainPage/components
SecretOverviewPage
SecretOverviewPage.tsx
components/ProjectIndexSecretsSection
SecretRotationPage
Signup
admin/SignUpPage
helm-charts/secrets-operator
k8-operator/controllers

@ -108,7 +108,7 @@ brews:
zsh_completion.install "completions/infisical.zsh" => "_infisical"
fish_completion.install "completions/infisical.fish"
man1.install "manpages/infisical.1.gz"
- name: 'infisical@{{.Version}}'
- name: "infisical@{{.Version}}"
tap:
owner: Infisical
name: homebrew-get-cli
@ -186,12 +186,14 @@ aurs:
# man pages
install -Dm644 "./manpages/infisical.1.gz" "${pkgdir}/usr/share/man/man1/infisical.1.gz"
# dockers:
# - dockerfile: cli/docker/Dockerfile
# goos: linux
# goarch: amd64
# ids:
# - infisical
# image_templates:
# - "infisical/cli:{{ .Version }}"
# - "infisical/cli:latest"
dockers:
- dockerfile: docker/alpine
goos: linux
goarch: amd64
ids:
- all-other-builds
image_templates:
- "infisical/cli:{{ .Version }}"
- "infisical/cli:{{ .Major }}.{{ .Minor }}"
- "infisical/cli:{{ .Major }}"
- "infisical/cli:latest"

@ -129,7 +129,7 @@ Note that this security address should be used only for undisclosed vulnerabilit
## Contributing
Whether it's big or small, we love contributions. Check out our guide to see how to [get started](https://infisical.com/docs/contributing/overview).
Whether it's big or small, we love contributions. Check out our guide to see how to [get started](https://infisical.com/docs/contributing/getting-started).
Not sure where to get started? You can:

@ -60,7 +60,7 @@
"pino": "^8.16.1",
"pino-http": "^8.5.1",
"posthog-node": "^2.6.0",
"probot": "^12.3.1",
"probot": "^12.3.3",
"query-string": "^7.1.3",
"rate-limit-mongo": "^2.3.2",
"rimraf": "^3.0.2",
@ -5991,9 +5991,9 @@
}
},
"node_modules/@octokit/webhooks": {
"version": "9.26.0",
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.0.tgz",
"integrity": "sha512-foZlsgrTDwAmD5j2Czn6ji10lbWjGDVsUxTIydjG9KTkAWKJrFapXJgO5SbGxRwfPd3OJdhK3nA2YPqVhxLXqA==",
"version": "9.26.3",
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.3.tgz",
"integrity": "sha512-DLGk+gzeVq5oK89Bo601txYmyrelMQ7Fi5EnjHE0Xs8CWicy2xkmnJMKptKJrBJpstqbd/9oeDFi/Zj2pudBDQ==",
"dependencies": {
"@octokit/request-error": "^2.0.2",
"@octokit/webhooks-methods": "^2.0.0",
@ -16306,9 +16306,9 @@
}
},
"node_modules/probot": {
"version": "12.3.1",
"resolved": "https://registry.npmjs.org/probot/-/probot-12.3.1.tgz",
"integrity": "sha512-ECSgycmAC0ILEK6cOa+x3QPufP5JybsuohOFCYr3glQU5SkbmypZJE/Sfio9mxAFHK5LCXveIDsfZCxf6ck4JA==",
"version": "12.3.3",
"resolved": "https://registry.npmjs.org/probot/-/probot-12.3.3.tgz",
"integrity": "sha512-cdtKd+xISzi8sw6++BYBXleRknCA6hqUMoHj/sJqQBrjbNxQLhfeFCq9O2d0Z4eShsy5YFRR3MWwDKJ9uAE0CA==",
"dependencies": {
"@octokit/core": "^3.2.4",
"@octokit/plugin-enterprise-compatibility": "^1.2.8",
@ -16317,7 +16317,7 @@
"@octokit/plugin-retry": "^3.0.6",
"@octokit/plugin-throttling": "^3.3.4",
"@octokit/types": "^8.0.0",
"@octokit/webhooks": "^9.8.4",
"@octokit/webhooks": "^9.26.3",
"@probot/get-private-key": "^1.1.0",
"@probot/octokit-plugin-config": "^1.0.0",
"@probot/pino": "^2.2.0",
@ -23392,9 +23392,9 @@
}
},
"@octokit/webhooks": {
"version": "9.26.0",
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.0.tgz",
"integrity": "sha512-foZlsgrTDwAmD5j2Czn6ji10lbWjGDVsUxTIydjG9KTkAWKJrFapXJgO5SbGxRwfPd3OJdhK3nA2YPqVhxLXqA==",
"version": "9.26.3",
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.3.tgz",
"integrity": "sha512-DLGk+gzeVq5oK89Bo601txYmyrelMQ7Fi5EnjHE0Xs8CWicy2xkmnJMKptKJrBJpstqbd/9oeDFi/Zj2pudBDQ==",
"requires": {
"@octokit/request-error": "^2.0.2",
"@octokit/webhooks-methods": "^2.0.0",
@ -31039,9 +31039,9 @@
}
},
"probot": {
"version": "12.3.1",
"resolved": "https://registry.npmjs.org/probot/-/probot-12.3.1.tgz",
"integrity": "sha512-ECSgycmAC0ILEK6cOa+x3QPufP5JybsuohOFCYr3glQU5SkbmypZJE/Sfio9mxAFHK5LCXveIDsfZCxf6ck4JA==",
"version": "12.3.3",
"resolved": "https://registry.npmjs.org/probot/-/probot-12.3.3.tgz",
"integrity": "sha512-cdtKd+xISzi8sw6++BYBXleRknCA6hqUMoHj/sJqQBrjbNxQLhfeFCq9O2d0Z4eShsy5YFRR3MWwDKJ9uAE0CA==",
"requires": {
"@octokit/core": "^3.2.4",
"@octokit/plugin-enterprise-compatibility": "^1.2.8",
@ -31050,7 +31050,7 @@
"@octokit/plugin-retry": "^3.0.6",
"@octokit/plugin-throttling": "^3.3.4",
"@octokit/types": "^8.0.0",
"@octokit/webhooks": "^9.8.4",
"@octokit/webhooks": "^9.26.3",
"@probot/get-private-key": "^1.1.0",
"@probot/octokit-plugin-config": "^1.0.0",
"@probot/pino": "^2.2.0",

@ -51,7 +51,7 @@
"pino": "^8.16.1",
"pino-http": "^8.5.1",
"posthog-node": "^2.6.0",
"probot": "^12.3.1",
"probot": "^12.3.3",
"query-string": "^7.1.3",
"rate-limit-mongo": "^2.3.2",
"rimraf": "^3.0.2",

File diff suppressed because it is too large Load Diff

@ -2,7 +2,7 @@ import { Request, Response } from "express";
import { Types } from "mongoose";
import { standardRequest } from "../../config/request";
import { getApps, getTeams, revokeAccess } from "../../integrations";
import { Bot, IntegrationAuth, Workspace } from "../../models";
import { Bot, IIntegrationAuth, Integration, IntegrationAuth, Workspace } from "../../models";
import { EventType } from "../../ee/models";
import { IntegrationService } from "../../services";
import { EEAuditLogService } from "../../ee/services";
@ -130,7 +130,6 @@ export const oAuthExchange = async (req: Request, res: Response) => {
export const saveIntegrationToken = async (req: Request, res: Response) => {
// TODO: refactor
// TODO: check if access token is valid for each integration
let integrationAuth;
const {
body: { workspaceId, integration, url, accessId, namespace, accessToken, refreshToken }
} = await validateRequest(reqValidator.SaveIntegrationAccessTokenV1, req);
@ -152,31 +151,21 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
if (!bot) throw new Error("Bot must be enabled to save integration access token");
integrationAuth = await IntegrationAuth.findOneAndUpdate(
{
workspace: new Types.ObjectId(workspaceId),
integration
},
{
workspace: new Types.ObjectId(workspaceId),
integration,
url,
namespace,
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8,
...(integration === INTEGRATION_GCP_SECRET_MANAGER
? {
metadata: {
authMethod: "serviceAccount"
}
let integrationAuth = await new IntegrationAuth({
workspace: new Types.ObjectId(workspaceId),
integration,
url,
namespace,
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8,
...(integration === INTEGRATION_GCP_SECRET_MANAGER
? {
metadata: {
authMethod: "serviceAccount"
}
: {})
},
{
new: true,
upsert: true
}
);
}
: {})
}).save();
// encrypt and save integration access details
if (refreshToken) {
@ -188,12 +177,12 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
// encrypt and save integration access details
if (accessId || accessToken) {
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
integrationAuth = (await IntegrationService.setIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString(),
accessId,
accessToken,
accessExpiresAt: undefined
});
})) as IIntegrationAuth;
}
if (!integrationAuth) throw new Error("Failed to save integration access token");
@ -1208,13 +1197,64 @@ export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res:
});
};
/**
* Delete all integration authorizations and integrations for workspace with id [workspaceId]
* with integration name [integration]
* @param req
* @param res
* @returns
*/
export const deleteIntegrationAuths = async (req: Request, res: Response) => {
const {
query: { integration, workspaceId }
} = await validateRequest(reqValidator.DeleteIntegrationAuthsV1, req);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Integrations
);
const integrationAuths = await IntegrationAuth.deleteMany({
integration,
workspace: new Types.ObjectId(workspaceId)
});
const integrations = await Integration.deleteMany({
integration,
workspace: new Types.ObjectId(workspaceId)
});
await EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.UNAUTHORIZE_INTEGRATION,
metadata: {
integration
}
},
{
workspaceId: new Types.ObjectId(workspaceId)
}
);
return res.status(200).send({
integrationAuths,
integrations
});
}
/**
* Delete integration authorization with id [integrationAuthId]
* @param req
* @param res
* @returns
*/
export const deleteIntegrationAuth = async (req: Request, res: Response) => {
export const deleteIntegrationAuthById = async (req: Request, res: Response) => {
const {
params: { integrationAuthId }
} = await validateRequest(reqValidator.DeleteIntegrationAuthV1, req);

@ -251,6 +251,21 @@ export const deleteIntegration = async (req: Request, res: Response) => {
});
if (!deletedIntegration) throw new Error("Failed to find integration");
const numOtherIntegrationsUsingSameAuth = await Integration.countDocuments({
integrationAuth: deletedIntegration.integrationAuth,
_id: {
$nin: [deletedIntegration._id]
}
});
if (numOtherIntegrationsUsingSameAuth === 0) {
// no other integrations are using the same integration auth
// -> delete integration auth associated with the integration being deleted
await IntegrationAuth.deleteOne({
_id: deletedIntegration.integrationAuth
});
}
await EEAuditLogService.createAuditLog(
req.authData,

@ -111,11 +111,17 @@ export const createSecretImp = async (req: Request, res: Response) => {
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment: secretImport.environment, secretPath: secretImport.secretPath })
);
}
const folders = await Folder.findOne({
@ -323,7 +329,7 @@ export const updateSecretImport = async (req: Request, res: Response) => {
authData: req.authData,
workspaceId: importSecDoc.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, {
@ -331,6 +337,13 @@ export const updateSecretImport = async (req: Request, res: Response) => {
secretPath
})
);
secretImports.forEach(({ environment, secretPath }) => {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
})
}
const orderBefore = importSecDoc.imports;
@ -453,7 +466,7 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
authData: req.authData,
workspaceId: importSecDoc.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.Secrets, {
@ -620,7 +633,7 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {
@ -677,7 +690,7 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
authData: req.authData,
workspaceId: importSecDoc.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {

@ -60,6 +60,52 @@ const packageUniversalAuthClientSecretData = (identityUniversalAuthClientSecret:
* @param res
*/
export const renewAccessToken = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Renew access token'
#swagger.description = 'Renew access token'
#swagger.requestBody = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"accessToken": {
"type": "string",
"description": "Access token to renew",
"example": "..."
}
}
}
}
}
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"accessToken": {
"type": "string",
"description": "(Same) Access token after successful renewal"
},
"expiresIn": {
"type": "number",
"description": "TTL of access token in seconds"
},
"tokenType": {
"type": "string",
"description": "Type of access token (e.g. Bearer)"
}
},
"description": "Access token and its details"
}
}
}
}
*/
const {
body: {
accessToken
@ -83,9 +129,14 @@ export const renewAccessToken = async (req: Request, res: Response) => {
accessTokenTTL,
accessTokenLastRenewedAt,
accessTokenMaxTTL,
createdAt: accessTokenCreatedAt
createdAt: accessTokenCreatedAt,
accessTokenNumUses,
accessTokenNumUsesLimit
} = identityAccessToken;
if (accessTokenNumUses >= accessTokenNumUsesLimit) {
throw BadRequestError({ message: "Unable to renew because access token number of uses limit reached" })
}
// ttl check
if (accessTokenTTL > 0) {
@ -150,6 +201,57 @@ export const renewAccessToken = async (req: Request, res: Response) => {
* @param res
*/
export const loginIdentityUniversalAuth = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Login with Universal Auth'
#swagger.description = 'Login with Universal Auth'
#swagger.requestBody = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"clientId": {
"type": "string",
"description": "Client ID for identity to login with Universal Auth",
"example": "..."
},
"clientSecret": {
"type": "string",
"description": "Client Secret for identity to login with Universal Auth",
"example": "..."
}
}
}
}
}
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"accessToken": {
"type": "string",
"description": "Access token issued after successful login"
},
"expiresIn": {
"type": "number",
"description": "TTL of access token in seconds"
},
"tokenType": {
"type": "string",
"description": "Type of access token (e.g. Bearer)"
}
},
"description": "Access token and its details"
}
}
}
}
*/
const {
body: {
clientId,
@ -303,7 +405,105 @@ export const loginIdentityUniversalAuth = async (req: Request, res: Response) =>
});
}
export const addIdentityUniversalAuth = async (req: Request, res: Response) => {
/**
* Attach identity universal auth method onto identity with id [identityId]
* @param req
* @param res
*/
export const attachIdentityUniversalAuth = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Attach Universal Auth configuration onto identity'
#swagger.description = 'Attach Universal Auth configuration onto identity'
#swagger.security = [{
"bearerAuth": []
}]
#swagger.parameters['identityId'] = {
"description": "ID of identity to attach Universal Auth onto",
"required": true,
"type": "string",
"in": "path"
}
#swagger.requestBody = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"clientSecretTrustedIps": {
type: "array",
items: {
type: "object",
"properties": {
"ipAddress": {
type: "string",
description: "IP address to trust",
default: "0.0.0.0/0"
}
}
},
"description": "List of IPs or CIDR ranges that the Client Secret can be used from together with the Client ID to get back an access token. By default, Client Secrets are given the 0.0.0.0/0 entry representing all possible IPv4 addresses.",
"example": "...",
"default": [{ ipAddress: "0.0.0.0/0" }]
},
"accessTokenTTL": {
"type": "number",
"description": "The incremental lifetime for an acccess token in seconds; a value of 0 implies an infinite incremental lifetime.",
"example": "...",
"default": 100
},
"accessTokenMaxTTL": {
"type": "number",
"description": "The maximum lifetime for an acccess token in seconds; a value of 0 implies an infinite maximum lifetime.",
"example": "...",
"default": 2592000
},
"accessTokenNumUsesLimit": {
"type": "number",
"description": "The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses.",
"example": "...",
"default": 0
},
"accessTokenTrustedIps": {
type: "array",
items: {
type: "object",
"properties": {
"ipAddress": {
type: "string",
description: "IP address to trust",
default: "0.0.0.0/0"
}
}
},
"description": "List of IPs or CIDR ranges that access tokens can be used from. By default, each token is given the 0.0.0.0/0 entry representing all possible IPv4 addresses.",
"example": "...",
"default": [{ ipAddress: "0.0.0.0/0" }]
}
}
}
}
}
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"identityUniversalAuth": {
$ref: '#/definitions/IdentityUniversalAuth'
}
},
"description": "Details of attached Universal Auth"
}
}
}
}
*/
const {
params: { identityId },
body: {
@ -350,7 +550,7 @@ export const addIdentityUniversalAuth = async (req: Request, res: Response) => {
// validate trusted ips
const reformattedClientSecretTrustedIps = clientSecretTrustedIps.map((clientSecretTrustedIp) => {
if (!plan.ipAllowlisting && clientSecretTrustedIp.ipAddress !== "0.0.0.0/0") return res.status(400).send({
if (!plan.ipAllowlisting && (clientSecretTrustedIp.ipAddress !== "0.0.0.0/0" && clientSecretTrustedIp.ipAddress !== "::/0")) return res.status(400).send({
message: "Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
});
@ -364,7 +564,7 @@ export const addIdentityUniversalAuth = async (req: Request, res: Response) => {
});
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
if (!plan.ipAllowlisting && accessTokenTrustedIp.ipAddress !== "0.0.0.0/0") return res.status(400).send({
if (!plan.ipAllowlisting && (accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" && accessTokenTrustedIp.ipAddress !== "::/0")) return res.status(400).send({
message: "Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
});
@ -414,7 +614,98 @@ export const addIdentityUniversalAuth = async (req: Request, res: Response) => {
});
}
/**
* Update identity universal auth method on identity with id [identityId]
* @param req
* @param res
*/
export const updateIdentityUniversalAuth = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Update Universal Auth configuration on identity'
#swagger.description = 'Update Universal Auth configuration on identity'
#swagger.security = [{
"bearerAuth": []
}]
#swagger.parameters['identityId'] = {
"description": "ID of identity to update Universal Auth on",
"required": true,
"type": "string",
"in": "path"
}
#swagger.requestBody = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"clientSecretTrustedIps": {
type: "array",
items: {
type: "object",
"properties": {
"ipAddress": {
type: "string",
description: "IP address to trust"
}
}
},
"description": "List of IPs or CIDR ranges that the Client Secret can be used from together with the Client ID to get back an access token. By default, Client Secrets are given the 0.0.0.0/0 entry representing all possible IPv4 addresses.",
"example": "...",
},
"accessTokenTTL": {
"type": "number",
"description": "The incremental lifetime for an acccess token in seconds; a value of 0 implies an infinite incremental lifetime.",
"example": "...",
},
"accessTokenMaxTTL": {
"type": "number",
"description": "The maximum lifetime for an acccess token in seconds; a value of 0 implies an infinite maximum lifetime.",
"example": "...",
},
"accessTokenNumUsesLimit": {
"type": "number",
"description": "The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses.",
"example": "...",
},
"accessTokenTrustedIps": {
type: "array",
items: {
type: "object",
"properties": {
"ipAddress": {
type: "string",
description: "IP address to trust"
}
}
},
"description": "List of IPs or CIDR ranges that access tokens can be used from. By default, each token is given the 0.0.0.0/0 entry representing all possible IPv4 addresses.",
"example": "...",
}
}
}
}
}
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"identityUniversalAuth": {
$ref: '#/definitions/IdentityUniversalAuth'
}
},
"description": "Details of updated Universal Auth"
}
}
}
}
*/
const {
params: { identityId },
body: {
@ -459,7 +750,7 @@ export const updateIdentityUniversalAuth = async (req: Request, res: Response) =
let reformattedClientSecretTrustedIps;
if (clientSecretTrustedIps) {
reformattedClientSecretTrustedIps = clientSecretTrustedIps.map((clientSecretTrustedIp) => {
if (!plan.ipAllowlisting && clientSecretTrustedIp.ipAddress !== "0.0.0.0/0") return res.status(400).send({
if (!plan.ipAllowlisting && (clientSecretTrustedIp.ipAddress !== "0.0.0.0/0" && clientSecretTrustedIp.ipAddress !== "::/0")) return res.status(400).send({
message: "Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
});
@ -476,7 +767,7 @@ export const updateIdentityUniversalAuth = async (req: Request, res: Response) =
let reformattedAccessTokenTrustedIps;
if (accessTokenTrustedIps) {
reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
if (!plan.ipAllowlisting && accessTokenTrustedIp.ipAddress !== "0.0.0.0/0") return res.status(400).send({
if (!plan.ipAllowlisting && (accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" && accessTokenTrustedIp.ipAddress !== "::/0")) return res.status(400).send({
message: "Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
});
@ -526,7 +817,43 @@ export const updateIdentityUniversalAuth = async (req: Request, res: Response) =
});
}
/**
* Return identity universal auth method on identity with id [identityId]
* @param req
* @param res
*/
export const getIdentityUniversalAuth = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Retrieve Universal Auth configuration on identity'
#swagger.description = 'Retrieve Universal Auth configuration on identity'
#swagger.security = [{
"bearerAuth": []
}]
#swagger.parameters['identityId'] = {
"description": "ID of identity to retrieve Universal Auth on",
"required": true,
"type": "string",
"in": "path"
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"identityUniversalAuth": {
$ref: '#/definitions/IdentityUniversalAuth'
}
},
"description": "Details of retrieved Universal Auth"
}
}
}
}
*/
const {
params: { identityId }
} = await validateRequest(reqValidator.GetUniversalAuthForIdentityV1, req);
@ -577,7 +904,77 @@ export const getIdentityUniversalAuth = async (req: Request, res: Response) => {
});
}
/**
* Create client secret for identity universal auth method on identity with id [identityId]
* @param req
* @param res
*/
export const createUniversalAuthClientSecret = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Create Universal Auth Client Secret for identity'
#swagger.description = 'Create Universal Auth Client Secret for identity'
#swagger.security = [{
"bearerAuth": []
}]
#swagger.parameters['identityId'] = {
"description": "ID of identity to create Universal Auth Client Secret for",
"required": true,
"type": "string",
"in": "path"
}
#swagger.requestBody = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "A description for the Client Secret to create.",
"example": "..."
},
"ttl": {
"type": "number",
"description": "The time-to-live for the Client Secret to create. By default, the TTL will be set to 0 which implies that the Client Secret will never expire; a value of 0 implies an infinite lifetime.",
"example": "...",
"default": 0
},
"numUsesLimit": {
"type": "number",
"description": "The maximum number of times that the Client Secret can be used together with the Client ID to get back an access token; a value of 0 implies infinite number of uses.",
"example": "...",
"default": 0
}
}
}
}
}
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"clientSecret": {
"type": "string",
"description": "The created Client Secret"
},
"clientSecretData": {
$ref: '#/definitions/IdentityUniversalAuthClientSecretData'
}
},
"description": "Details of the created Client Secret"
}
}
}
}
*/
const {
params: { identityId },
body: {
@ -660,7 +1057,46 @@ export const createUniversalAuthClientSecret = async (req: Request, res: Respons
});
}
export const getUniversalAuthClientSecrets = async (req: Request, res: Response) => {
/**
* Return list of client secret details for identity universal auth method on identity with id [identityId]
* @param req
* @param res
*/
export const getUniversalAuthClientSecretsDetails = async (req: Request, res: Response) => {
/*
#swagger.summary = 'List Universal Auth Client Secrets for identity'
#swagger.description = 'List Universal Auth Client Secrets for identity'
#swagger.security = [{
"bearerAuth": []
}]
#swagger.parameters['identityId'] = {
"description": "ID of identity for which to get Client Secrets for",
"required": true,
"type": "string",
"in": "path"
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"clientSecretData": {
type: "array",
items: {
$ref: '#/definitions/IdentityUniversalAuthClientSecretData'
}
}
},
"description": "Details of the Client Secrets"
}
}
}
}
*/
const {
params: { identityId }
} = await validateRequest(reqValidator.GetUniversalAuthClientSecretsV1, req);
@ -720,7 +1156,50 @@ export const getUniversalAuthClientSecrets = async (req: Request, res: Response)
});
}
/**
* Revoke client secret for identity universal auth method on identity with id [identityId]
* @param req
* @param res
*/
export const revokeUniversalAuthClientSecret = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Revoke Universal Auth Client Secret for identity'
#swagger.description = 'Revoke Universal Auth Client Secret for identity'
#swagger.security = [{
"bearerAuth": []
}]
#swagger.parameters['identityId'] = {
"description": "ID of identity under which Client Secret was issued for",
"required": true,
"type": "string",
"in": "path"
}
#swagger.parameters['clientSecretId'] = {
"description": "ID of Client Secret to revoke",
"required": true,
"type": "string",
"in": "path"
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"clientSecretData": {
$ref: '#/definitions/IdentityUniversalAuthClientSecretData'
}
},
"description": "Details of the revoked Client Secret"
}
}
}
}
*/
const {
params: { identityId, clientSecretId }
} = await validateRequest(reqValidator.RevokeUniversalAuthClientSecretV1, req);

@ -1,9 +1,13 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import {
IdentityMembershipOrg,
Membership,
IWorkspace,
Identity,
IdentityMembership,
IdentityMembershipOrg,
Membership,
MembershipOrg,
User,
Workspace
} from "../../models";
import { Role } from "../../ee/models";
@ -33,11 +37,12 @@ import { ForbiddenError } from "@casl/ability";
*/
export const getOrganizationMemberships = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Return organization memberships'
#swagger.description = 'Return organization memberships'
#swagger.summary = 'Return organization user memberships'
#swagger.description = 'Return organization user memberships'
#swagger.security = [{
"apiKeyAuth": []
"apiKeyAuth": [],
"bearerAuth": []
}]
#swagger.parameters['organizationId'] = {
@ -94,11 +99,12 @@ export const getOrganizationMemberships = async (req: Request, res: Response) =>
*/
export const updateOrganizationMembership = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Update organization membership'
#swagger.description = 'Update organization membership'
#swagger.summary = 'Update organization user membership'
#swagger.description = 'Update organization user membership'
#swagger.security = [{
"apiKeyAuth": []
"apiKeyAuth": [],
"bearerAuth": []
}]
#swagger.parameters['organizationId'] = {
@ -214,11 +220,12 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
*/
export const deleteOrganizationMembership = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Delete organization membership'
#swagger.description = 'Delete organization membership'
#swagger.summary = 'Delete organization user membership'
#swagger.description = 'Delete organization user membership'
#swagger.security = [{
"apiKeyAuth": []
"apiKeyAuth": [],
"bearerAuth": []
}]
#swagger.parameters['organizationId'] = {
@ -295,7 +302,8 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
#swagger.description = 'Return projects in organization that user is part of'
#swagger.security = [{
"apiKeyAuth": []
"apiKeyAuth": [],
"bearerAuth": []
}]
#swagger.parameters['organizationId'] = {
@ -323,6 +331,7 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
}
}
*/
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgWorkspacesv2, req);
@ -348,13 +357,27 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
).map((w) => w._id.toString())
);
const workspaces = (
await Membership.find({
user: req.user._id
}).populate("workspace")
)
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace);
let workspaces: IWorkspace[] = [];
if (req.authData.authPayload instanceof Identity) {
workspaces = (
await IdentityMembership.find({
identity: req.authData.authPayload._id
}).populate<{ workspace: IWorkspace }>("workspace")
)
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace);
}
if (req.authData.authPayload instanceof User) {
workspaces = (
await Membership.find({
user: req.authData.authPayload._id
}).populate<{ workspace: IWorkspace }>("workspace")
)
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace);
}
return res.status(200).send({
workspaces
@ -425,6 +448,40 @@ export const deleteOrganizationById = async (req: Request, res: Response) => {
* @returns
*/
export const getOrganizationIdentityMemberships = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Return organization identity memberships'
#swagger.description = 'Return organization identity memberships'
#swagger.security = [{
"bearerAuth": []
}]
#swagger.parameters['organizationId'] = {
"description": "ID of organization",
"required": true,
"type": "string",
"in": "path"
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"identityMemberships": {
"type": "array",
"items": {
$ref: "#/components/schemas/IdentityMembershipOrg"
},
"description": "Identity memberships of organization"
}
}
}
}
}
}
*/
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgIdentityMembershipsV2, req);

@ -13,7 +13,7 @@ import {
ProjectPermissionSub,
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import { ForbiddenError, subject } from "@casl/ability";
import { Types } from "mongoose";
/**
@ -86,6 +86,14 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
ProjectPermissionSub.ServiceTokens
);
scopes.forEach(({ environment, secretPath }) => {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment, secretPath: secretPath })
);
})
const secret = crypto.randomBytes(16).toString("hex");
const secretHash = await bcrypt.hash(secret, await getSaltRounds());

@ -249,11 +249,12 @@ export const getWorkspaceServiceTokenData = async (req: Request, res: Response)
*/
export const getWorkspaceMemberships = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Return project memberships'
#swagger.description = 'Return project memberships'
#swagger.summary = 'Return project user memberships'
#swagger.description = 'Return project user memberships'
#swagger.security = [{
"apiKeyAuth": []
"apiKeyAuth": [],
"bearerAuth": []
}]
#swagger.parameters['workspaceId'] = {
@ -312,11 +313,12 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
*/
export const updateWorkspaceMembership = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Update project membership'
#swagger.description = 'Update project membership'
#swagger.summary = 'Update project user membership'
#swagger.description = 'Update project user membership'
#swagger.security = [{
"apiKeyAuth": []
"apiKeyAuth": [],
"bearerAuth": []
}]
#swagger.parameters['workspaceId'] = {
@ -340,7 +342,7 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
"properties": {
"role": {
"type": "string",
"description": "Role of membership - either admin or member",
"description": "Role to update to for project membership",
}
}
}
@ -402,11 +404,12 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
*/
export const deleteWorkspaceMembership = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Delete project membership'
#swagger.description = 'Delete project membership'
#swagger.summary = 'Delete project user membership'
#swagger.description = 'Delete project user membership'
#swagger.security = [{
"apiKeyAuth": []
"apiKeyAuth": [],
"bearerAuth": []
}]
#swagger.parameters['workspaceId'] = {
@ -594,7 +597,60 @@ export const addIdentityToWorkspace = async (req: Request, res: Response) => {
* @param req
* @param res
*/
export const updateIdentityWorkspaceRole = async (req: Request, res: Response) => {
export const updateIdentityWorkspaceRole = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Update project identity membership'
#swagger.description = 'Update project identity membership'
#swagger.security = [{
"bearerAuth": []
}]
#swagger.parameters['workspaceId'] = {
"description": "ID of project",
"required": true,
"type": "string"
}
#swagger.parameters['identityId'] = {
"description": "ID of identity whose membership to update in project",
"required": true,
"type": "string"
}
#swagger.requestBody = {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"role": {
"type": "string",
"description": "Role to update to for identity project membership",
}
}
}
}
}
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"identityMembership": {
$ref: "#/components/schemas/IdentityMembership",
"description": "Updated identity membership"
}
}
}
}
}
}
*/
const {
params: { workspaceId, identityId },
body: {
@ -676,12 +732,48 @@ export const updateIdentityWorkspaceRole = async (req: Request, res: Response) =
}
/**
* Delete identity with id [identityId] to workspace
* Delete identity with id [identityId] from workspace
* with id [workspaceId]
* @param req
* @param res
*/
export const deleteIdentityFromWorkspace = async (req: Request, res: Response) => {
export const deleteIdentityFromWorkspace = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Delete project identity membership'
#swagger.description = 'Delete project identity membership'
#swagger.security = [{
"bearerAuth": []
}]
#swagger.parameters['workspaceId'] = {
"description": "ID of project",
"required": true,
"type": "string"
}
#swagger.parameters['identityId'] = {
"description": "ID of identity whose membership to delete in project",
"required": true,
"type": "string"
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"identityMembership": {
$ref: "#/components/schemas/IdentityMembership",
"description": "Deleted identity membership"
}
}
}
}
}
}
*/
const {
params: { workspaceId, identityId }
} = await validateRequest(reqValidator.DeleteIdentityFromWorkspaceV2, req);
@ -732,7 +824,41 @@ export const deleteIdentityFromWorkspace = async (req: Request, res: Response) =
* @param res
* @returns
*/
export const getWorkspaceIdentityMemberships = async (req: Request, res: Response) => {
export const getWorkspaceIdentityMemberships = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Return project identity memberships'
#swagger.description = 'Return project identity memberships'
#swagger.security = [{
"bearerAuth": []
}]
#swagger.parameters['workspaceId'] = {
"description": "ID of project",
"required": true,
"type": "string",
"in": "path"
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"identityMemberships": {
"type": "array",
"items": {
$ref: "#/components/schemas/IdentityMembership"
},
"description": "Identity memberships of project"
}
}
}
}
}
}
*/
const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceIdentityMembersV2, req);

@ -348,7 +348,7 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
}
*/
const {
query: { secretPath, environment, workspaceId, type, include_imports },
query: { secretPath, environment, workspaceId, type, include_imports, version },
params: { secretName }
} = await validateRequest(reqValidator.GetSecretByNameRawV3, req);
@ -371,7 +371,8 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
type,
secretPath,
authData: req.authData,
include_imports
include_imports,
version
});
const key = await BotService.getWorkspaceKeyWithBot({
@ -865,7 +866,7 @@ export const getSecrets = async (req: Request, res: Response) => {
*/
export const getSecretByName = async (req: Request, res: Response) => {
const {
query: { secretPath, environment, workspaceId, type, include_imports },
query: { secretPath, environment, workspaceId, type, include_imports, version },
params: { secretName }
} = await validateRequest(reqValidator.GetSecretByNameV3, req);
@ -888,7 +889,8 @@ export const getSecretByName = async (req: Request, res: Response) => {
type,
secretPath,
authData: req.authData,
include_imports
include_imports,
version
});
return res.status(200).send({

@ -42,6 +42,58 @@ import { ForbiddenError } from "@casl/ability";
* @returns
*/
export const createIdentity = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Create identity'
#swagger.description = 'Create identity'
#swagger.security = [{
"bearerAuth": []
}]
#swagger.requestBody = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name of entity to create",
"example": "development"
},
"organizationId": {
"type": "string",
"description": "ID of organization where to create identity",
"example": "dev-environment"
},
"role": {
"type": "string",
"description": "Role to assume for organization membership",
"example": "no-access"
}
},
"required": ["name", "organizationId", "role"]
}
}
}
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"identity": {
$ref: '#/definitions/Identity'
}
},
"description": "Details of the created identity"
}
}
}
}
*/
const {
body: {
name,
@ -120,6 +172,59 @@ export const createIdentity = async (req: Request, res: Response) => {
* @returns
*/
export const updateIdentity = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Update identity'
#swagger.description = 'Update identity'
#swagger.security = [{
"bearerAuth": []
}]
#swagger.parameters['identityId'] = {
"description": "ID of identity to update",
"required": true,
"type": "string",
"in": "path"
}
#swagger.requestBody = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name of entity to update to",
"example": "development"
},
"role": {
"type": "string",
"description": "Role to update to for organization membership",
"example": "no-access"
}
}
}
}
}
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"identity": {
$ref: '#/definitions/Identity'
}
},
"description": "Details of the updated identity"
}
}
}
}
*/
const {
params: { identityId },
body: {
@ -242,6 +347,37 @@ export const createIdentity = async (req: Request, res: Response) => {
* @returns
*/
export const deleteIdentity = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Delete identity'
#swagger.description = 'Delete identity'
#swagger.security = [{
"bearerAuth": []
}]
#swagger.parameters['identityId'] = {
"description": "ID of identity",
"required": true,
"type": "string",
"in": "path"
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"identity": {
$ref: '#/definitions/Identity'
}
},
"description": "Details of the deleted identity"
}
}
}
}
*/
const {
params: { identityId }
} = await validateRequest(reqValidator.DeleteIdentityV1, req);

@ -17,12 +17,12 @@ export const getSecretApprovalRequestCount = async (req: Request, res: Response)
} = await validateRequest(reqValidator.getSecretApprovalRequestCount, req);
if (!(req.authData.authPayload instanceof User)) return;
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (!membership) throw UnauthorizedRequestError();
const approvalRequestCount = await SecretApprovalRequest.aggregate([
@ -73,12 +73,12 @@ export const getSecretApprovalRequests = async (req: Request, res: Response) =>
} = await validateRequest(reqValidator.getSecretApprovalRequests, req);
if (!(req.authData.authPayload instanceof User)) return;
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (!membership) throw UnauthorizedRequestError();
const query = {
@ -168,13 +168,13 @@ export const getSecretApprovalRequestDetails = async (req: Request, res: Respons
user: req.authData.authPayload._id,
workspace: secretApprovalRequest.workspace
});
if (!membership) throw UnauthorizedRequestError();
// allow to fetch only if its admin or is the committer or approver
if (
membership.role !== "admin" &&
secretApprovalRequest.committer !== membership.id &&
!secretApprovalRequest.committer.equals(membership.id) &&
!secretApprovalRequest.policy.approvers.find(
(approverId) => approverId.toString() === membership._id.toString()
)
@ -215,7 +215,7 @@ export const updateSecretApprovalReviewStatus = async (req: Request, res: Respon
user: req.authData.authPayload._id,
workspace: secretApprovalRequest.workspace
});
if (!membership) throw UnauthorizedRequestError();
if (
@ -257,7 +257,7 @@ export const mergeSecretApprovalRequest = async (req: Request, res: Response) =>
user: req.authData.authPayload._id,
workspace: secretApprovalRequest.workspace
});
if (!membership) throw UnauthorizedRequestError();
if (
@ -307,7 +307,7 @@ export const updateSecretApprovalRequestStatus = async (req: Request, res: Respo
user: req.authData.authPayload._id,
workspace: secretApprovalRequest.workspace
});
if (!membership) throw UnauthorizedRequestError();
if (

@ -62,15 +62,30 @@ export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) =
#swagger.description = 'Return project secret snapshots ids'
#swagger.security = [{
"apiKeyAuth": []
"apiKeyAuth": [],
"bearerAuth": []
}]
#swagger.parameters['workspaceId'] = {
"description": "ID of project",
"description": "ID of project where to get secret snapshots for",
"required": true,
"type": "string"
}
#swagger.parameters['environment'] = {
"description": "Slug of environment where to get secret snapshots for",
"required": true,
"type": "string",
"in": "query"
}
#swagger.parameters['directory'] = {
"description": "Path where to get secret snapshots for like / or /foo/bar. Default is /",
"required": false,
"type": "string",
"in": "query"
}
#swagger.parameters['offset'] = {
"description": "Number of secret snapshots to skip",
"required": false,
@ -195,11 +210,12 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
#swagger.description = 'Roll back project secrets to those captured in a secret snapshot version.'
#swagger.security = [{
"apiKeyAuth": []
"apiKeyAuth": [],
"bearerAuth": []
}]
#swagger.parameters['workspaceId'] = {
"description": "ID of project",
"description": "ID of project where to roll back",
"required": true,
"type": "string"
}
@ -211,6 +227,14 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
"schema": {
"type": "object",
"properties": {
"environment": {
"type": "string",
"description": "Slug of environment where to roll back"
},
"directory": {
"type": "string",
"description": "Path where to roll back for like / or /foo/bar. Default is /"
},
"version": {
"type": "integer",
"description": "Version of secret snapshot to roll back to",

@ -8,7 +8,10 @@ export enum UserAgentType {
WEB = "web",
CLI = "cli",
K8_OPERATOR = "k8-operator",
OTHER = "other"
TERRAFORM = "terraform",
OTHER = "other",
PYTHON_SDK = "InfisicalPythonSDK",
NODE_SDK = "InfisicalNodeSDK"
}
export enum EventType {

@ -7,7 +7,7 @@ import { workspaceController } from "../../controllers/v1";
router.get(
"/:workspaceId/secret-snapshots",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
workspaceController.getWorkspaceSecretSnapshots
);
@ -23,7 +23,7 @@ router.get(
router.post(
"/:workspaceId/secret-snapshots/rollback",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
workspaceController.rollbackWorkspaceSecretSnapshot
);
@ -31,7 +31,7 @@ router.post(
router.get(
"/:workspaceId/audit-logs",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
workspaceController.getWorkspaceAuditLogs
);

@ -13,7 +13,7 @@ import {
SECRET_SHARED
} from "../variables";
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
import { InternalServerError } from "../utils/errors";
import { BotNotFoundError, InternalServerError } from "../utils/errors";
import { Folder } from "../models";
import { getFolderByPath } from "../services/FolderService";
import { getAllImportedSecrets } from "../services/SecretImportService";
@ -223,7 +223,7 @@ export const getKey = async ({ workspaceId }: { workspaceId: Types.ObjectId }) =
workspace: workspaceId
}).populate<{ sender: IUser }>("sender", "publicKey");
if (!botKey) throw new Error("Failed to find bot key");
if (!botKey) throw BotNotFoundError({ message: `getKey: Failed to find bot key for [workspaceId=${workspaceId}]` })
const bot = await Bot.findOne({
workspace: workspaceId

@ -10,7 +10,7 @@ export const apiLimiter = rateLimit({
// errorHandler: console.error.bind(null, 'rate-limit-mongo')
// }),
windowMs: 60 * 1000,
max: 350,
max: 480,
standardHeaders: true,
legacyHeaders: false,
skip: (request) => {
@ -30,7 +30,7 @@ const authLimit = rateLimit({
// collectionName: "expressRateRecords-authLimit",
// }),
windowMs: 60 * 1000,
max: 100,
max: 300,
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req, res) => {
@ -46,8 +46,8 @@ export const passwordLimiter = rateLimit({
// errorHandler: console.error.bind(null, 'rate-limit-mongo'),
// collectionName: "expressRateRecords-passwordLimiter",
// }),
windowMs: 60 * 60 * 1000,
max: 10,
windowMs: 60 * 1000,
max: 300,
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req, res) => {

@ -579,7 +579,9 @@ export const getSecretsHelper = async ({
event: "secrets pulled",
distinctId: await TelemetryService.getDistinctId({ authData }),
properties: {
numberOfSecrets: shouldRecordK8Event ? approximateForNoneCapturedEvents : secrets.length,
numberOfSecrets: shouldRecordK8Event
? approximateForNoneCapturedEvents
: secrets.length,
environment,
workspaceId,
folderId,
@ -611,42 +613,86 @@ export const getSecretHelper = async ({
type,
authData,
secretPath = "/",
include_imports = true
include_imports = true,
version
}: GetSecretParams) => {
const secretBlindIndex = await generateSecretBlindIndexHelper({
secretName,
workspaceId: new Types.ObjectId(workspaceId)
});
let secret: ISecret | null | undefined = null;
// if using service token filter towards the folderId by secretpath
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
// try getting personal secret first (if exists)
secret = await Secret.findOne({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
environment,
folder: folderId,
type: type ?? SECRET_PERSONAL,
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
}).lean();
if (!secret) {
// case: failed to find personal secret matching criteria
// -> find shared secret matching criteria
if (version === undefined) {
secret = await Secret.findOne({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
environment,
folder: folderId,
type: SECRET_SHARED
type: type ?? SECRET_PERSONAL,
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
}).lean();
} else {
const secretVersion = await SecretVersion.findOne({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
environment,
folder: folderId,
type: type ?? SECRET_PERSONAL,
version
}).lean();
if (secretVersion) {
secret = await new Secret({
...secretVersion,
_id: secretVersion?.secret
});
}
}
if (!secret) {
// case: failed to find personal secret matching criteria
// -> find shared secret matching criteria
if (version === undefined) {
secret = await Secret.findOne({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
environment,
folder: folderId,
type: SECRET_SHARED
}).lean();
} else {
const secretVersion = await SecretVersion.findOne({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
environment,
folder: folderId,
type: SECRET_SHARED,
version
}).lean();
if (secretVersion) {
secret = await new Secret({
...secretVersion,
_id: secretVersion?.secret
});
}
}
}
if (!secret && include_imports) {
// if still no secret found search in imported secret and retreive
secret = await getAnImportedSecret(secretName, workspaceId.toString(), environment, folderId);
secret = await getAnImportedSecret(
secretName,
workspaceId.toString(),
environment,
folderId,
version
);
}
if (!secret) throw SecretNotFoundError();
@ -1141,11 +1187,12 @@ const recursivelyExpandSecret = async (
const secRefKey = entities[entities.length - 1];
const val = await fetchCrossEnv(secRefEnv, secRefPath, secRefKey);
interpolatedValue = interpolatedValue.replaceAll(interpolationSyntax, val);
if (val !== undefined) {
interpolatedValue = interpolatedValue.replaceAll(interpolationSyntax, val);
}
}
}
}
expandedSec[key] = interpolatedValue;
return interpolatedValue;
};

@ -38,6 +38,7 @@ export interface GetSecretParams {
type?: "shared" | "personal";
authData: AuthData;
include_imports?: boolean;
version?: number;
}
export interface UpdateSecretParams {

@ -156,12 +156,20 @@ router.get(
integrationAuthController.getIntegrationAuthTeamCityBuildConfigs
);
router.delete(
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
integrationAuthController.deleteIntegrationAuths
);
router.delete(
"/:integrationAuthId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
integrationAuthController.deleteIntegrationAuth
integrationAuthController.deleteIntegrationAuthById
);
export default router;

@ -7,7 +7,7 @@ import { AuthMode } from "../../variables";
router.post(
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
secretImpsController.createSecretImp
);
@ -15,7 +15,7 @@ router.post(
router.put(
"/:id",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
secretImpsController.updateSecretImport
);
@ -23,7 +23,7 @@ router.put(
router.delete(
"/:id",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
secretImpsController.deleteSecretImport
);
@ -31,7 +31,7 @@ router.delete(
router.get(
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
secretImpsController.getSecretImports
);

@ -12,7 +12,7 @@ import { AuthMode } from "../../variables";
router.post(
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
createFolder
);
@ -20,7 +20,7 @@ router.post(
router.patch(
"/:folderName",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
updateFolderById
);
@ -28,7 +28,7 @@ router.patch(
router.delete(
"/:folderName",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
deleteFolder
);
@ -36,7 +36,7 @@ router.delete(
router.get(
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
getFolders
);

@ -18,15 +18,15 @@ router.post(
router.post(
"/universal-auth/identities/:identityId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
universalAuthController.addIdentityUniversalAuth
universalAuthController.attachIdentityUniversalAuth
);
router.patch(
"/universal-auth/identities/:identityId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
universalAuthController.updateIdentityUniversalAuth
);
@ -34,7 +34,7 @@ router.patch(
router.get(
"/universal-auth/identities/:identityId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
universalAuthController.getIdentityUniversalAuth
);
@ -42,7 +42,7 @@ router.get(
router.post(
"/universal-auth/identities/:identityId/client-secrets",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
universalAuthController.createUniversalAuthClientSecret
);
@ -50,15 +50,15 @@ router.post(
router.get(
"/universal-auth/identities/:identityId/client-secrets",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
universalAuthController.getUniversalAuthClientSecrets
universalAuthController.getUniversalAuthClientSecretsDetails
);
router.post(
"/universal-auth/identities/:identityId/client-secrets/:clientSecretId/revoke",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
universalAuthController.revokeUniversalAuthClientSecret
);

@ -7,7 +7,7 @@ import { AuthMode } from "../../variables";
router.post(
"/:workspaceId/environments",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
environmentController.createWorkspaceEnvironment
);
@ -15,7 +15,7 @@ router.post(
router.put(
"/:workspaceId/environments",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
environmentController.renameWorkspaceEnvironment
);
@ -23,7 +23,7 @@ router.put(
router.patch(
"/:workspaceId/environments",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
environmentController.reorderWorkspaceEnvironments
);
@ -31,7 +31,7 @@ router.patch(
router.delete(
"/:workspaceId/environments",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
environmentController.deleteWorkspaceEnvironment
);

@ -9,7 +9,7 @@ import { organizationsController } from "../../controllers/v2";
router.get(
"/:organizationId/memberships",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
organizationsController.getOrganizationMemberships
);
@ -17,7 +17,7 @@ router.get(
router.patch(
"/:organizationId/memberships/:membershipId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
organizationsController.updateOrganizationMembership
);
@ -25,7 +25,7 @@ router.patch(
router.delete(
"/:organizationId/memberships/:membershipId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
organizationsController.deleteOrganizationMembership
);
@ -33,7 +33,7 @@ router.delete(
router.get(
"/:organizationId/workspaces",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
organizationsController.getOrganizationWorkspaces
);

@ -62,7 +62,7 @@ router.get(
// new - TODO: rewire dashboard to this route
"/:workspaceId/memberships",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
workspaceController.getWorkspaceMemberships
);
@ -71,7 +71,7 @@ router.patch(
// TODO - rewire dashboard to this route
"/:workspaceId/memberships/:membershipId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
workspaceController.updateWorkspaceMembership
);
@ -80,7 +80,7 @@ router.delete(
// TODO - rewire dashboard to this route
"/:workspaceId/memberships/:membershipId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
workspaceController.deleteWorkspaceMembership
);
@ -96,7 +96,7 @@ router.patch(
router.post(
"/:workspaceId/identity-memberships/:identityId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
workspaceController.addIdentityToWorkspace
);
@ -104,7 +104,7 @@ router.post(
router.patch(
"/:workspaceId/identity-memberships/:identityId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
workspaceController.updateIdentityWorkspaceRole
);
@ -112,7 +112,7 @@ router.patch(
router.delete(
"/:workspaceId/identity-memberships/:identityId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
workspaceController.deleteIdentityFromWorkspace
);
@ -120,7 +120,7 @@ router.delete(
router.get(
"/:workspaceId/identity-memberships",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
}),
workspaceController.getWorkspaceIdentityMemberships
);

@ -1,5 +1,6 @@
import { Types } from "mongoose";
import { generateSecretBlindIndexHelper } from "../helpers";
import { SecretVersion } from "../ee/models";
import { Folder, ISecret, Secret, SecretImport } from "../models";
import { getFolderByPath } from "./FolderService";
@ -9,7 +10,8 @@ export const getAnImportedSecret = async (
secretName: string,
workspaceId: string,
environment: string,
folderId = "root"
folderId = "root",
version?: number
) => {
const secretBlindIndex = await generateSecretBlindIndexHelper({
secretName,
@ -48,10 +50,26 @@ export const getAnImportedSecret = async (
});
if (importedSecByFid.length === 0) return;
const secret = await Secret.findOne({
workspace: workspaceId,
secretBlindIndex
}).or(importedSecByFid.map(({ environment, folderId }) => ({ environment, folder: folderId }))).lean()
let secret;
if (version === undefined) {
secret = await Secret.findOne({
workspace: workspaceId,
secretBlindIndex
}).or(importedSecByFid.map(({ environment, folderId }) => ({ environment, folder: folderId }))).lean()
} else {
const secretVersion = await SecretVersion.findOne({
workspace: workspaceId,
secretBlindIndex,
version
}).or(importedSecByFid.map(({ environment, folderId }) => ({ environment, folder: folderId }))).lean();
if (secretVersion) {
secret = await new Secret({
...secretVersion,
_id: secretVersion.secret,
});
}
}
return secret;
};

@ -8,12 +8,12 @@ import {
getTelemetryEnabled,
} from "../config";
import {
Identity,
ServiceTokenData,
User,
User
} from "../models";
import {
AccountNotFoundError,
BadRequestError,
} from "../utils/errors";
class Telemetry {
@ -22,7 +22,7 @@ class Telemetry {
*/
static logTelemetryMessage = async () => {
if(!(await getTelemetryEnabled())){
if (!(await getTelemetryEnabled())) {
[
"To improve, Infisical collects telemetry data about general usage.",
"This helps us understand how the product is doing and guide our product development to create the best possible platform; it also helps us demonstrate growth as we support Infisical as open-source software.",
@ -42,8 +42,8 @@ class Telemetry {
postHogClient = new PostHog(await getPostHogProjectApiKey(), {
host: await getPostHogHost(),
});
}
}
return postHogClient;
}
@ -52,6 +52,7 @@ class Telemetry {
}: {
authData: AuthData;
}) => {
let distinctId = "";
if (authData.authPayload instanceof User) {
distinctId = authData.authPayload.email;
@ -59,14 +60,14 @@ class Telemetry {
if (authData.authPayload.user) {
const user = await User.findById(authData.authPayload.user, "email");
if (!user) throw AccountNotFoundError();
distinctId = user.email;
distinctId = user.email;
}
} else if (authData.authPayload instanceof Identity) {
distinctId = `identity-${authData.authPayload._id.toString()}`
} else {
distinctId = "unknown-auth-data"
}
if (distinctId === "") throw BadRequestError({
message: "Failed to obtain distinct id for logging telemetry",
});
return distinctId;
}
}

@ -75,7 +75,7 @@ export const initializeSamlStrategy = async () => {
const organization = await Organization.findById(req.ssoConfig.organization);
if (!organization) return done(OrganizationNotFoundError());
const email = profile.email;
const firstName = profile.firstName;
const lastName = profile.lastName;
@ -154,6 +154,7 @@ export const initializeSamlStrategy = async () => {
firstName,
lastName,
organizationName: organization?.name,
organizationId: organization?._id,
authMethod: req.ssoConfig.authProvider,
isUserCompleted,
...(req.body.RelayState ? {

@ -7,8 +7,14 @@ export const getUserAgentType = function (userAgent: string | undefined) {
return UserAgentType.CLI;
} else if (userAgent == UserAgentType.K8_OPERATOR) {
return UserAgentType.K8_OPERATOR;
} else if (userAgent == UserAgentType.TERRAFORM) {
return UserAgentType.TERRAFORM;
} else if (userAgent.toLowerCase().includes("mozilla")) {
return UserAgentType.WEB;
} else if (userAgent.includes(UserAgentType.NODE_SDK)) {
return UserAgentType.NODE_SDK;
} else if (userAgent.includes(UserAgentType.PYTHON_SDK)) {
return UserAgentType.PYTHON_SDK;
} else {
return UserAgentType.OTHER;
}

@ -108,14 +108,14 @@ export const AddUniversalAuthToIdentityV1 = z.object({
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }]),
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim(),
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }]),
.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),
@ -149,7 +149,7 @@ export const UpdateUniversalAuthToIdentityV1 = z.object({
accessTokenNumUsesLimit: z.number().int().min(0).optional(),
accessTokenMaxTTL: z.number().int().refine(value => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number",
}).default(2592000),
}).optional(),
}),
});

@ -192,6 +192,13 @@ export const GetIntegrationAuthNorthflankSecretGroupsV1 = z.object({
})
});
export const DeleteIntegrationAuthsV1 = z.object({
query: z.object({
integration: z.string().trim(),
workspaceId: z.string().trim()
})
});
export const DeleteIntegrationAuthV1 = z.object({
params: z.object({
integrationAuthId: z.string().trim()

@ -246,7 +246,15 @@ export const GetSecretByNameRawV3 = z.object({
include_imports: z
.enum(["true", "false"])
.default("true")
.transform((value) => value === "true")
.transform((value) => value === "true"),
version: z
.string()
.trim()
.optional()
.transform((value) => value === undefined ? undefined : parseInt(value, 10))
.refine((value) => value === undefined || !isNaN(value), {
message: "Version must be a number",
})
})
});
@ -318,7 +326,15 @@ export const GetSecretByNameV3 = z.object({
include_imports: z
.enum(["true", "false"])
.default("true")
.transform((value) => value === "true")
.transform((value) => value === "true"),
version: z
.string()
.trim()
.optional()
.transform((value) => value === undefined ? undefined : parseInt(value, 10))
.refine((value) => value === undefined || !isNaN(value), {
message: "Version must be a number",
})
}),
params: z.object({
secretName: z.string().trim()

@ -158,7 +158,7 @@ export const CreateServiceTokenV2 = z.object({
encryptedKey: z.string().trim(),
iv: z.string().trim(),
tag: z.string().trim(),
expiresIn: z.number(),
expiresIn: z.number().nullable().optional(),
permissions: z.enum(["read", "write"]).array()
})
});

@ -30,7 +30,7 @@ const generateOpenAPISpec = async () => {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
description: "A service token in Infisical"
description: "An access token in Infisical"
},
apiKeyAuth: {
type: "apiKey",
@ -52,6 +52,41 @@ const generateOpenAPISpec = async () => {
updatedAt: "2023-01-13T14:16:12.210Z",
createdAt: "2023-01-13T14:16:12.210Z"
},
Identity: {
_id: "",
name: "Machine 1",
authMethod: "universal-auth"
},
IdentityUniversalAuth: {
_id: "",
identity: "",
clientId: "...",
clientSecretTrustedIps: [{
ipAddress: "0.0.0.0",
type: "ipv4",
prefix: "0"
}],
accessTokenTTL: 7200,
accessTokenMaxTTL: 2592000,
accessTokenNumUsesLimit: 0,
accessTokenTrustedIps: [{
ipAddress: "0.0.0.0",
type: "ipv4",
prefix: "0"
}]
},
IdentityUniversalAuthClientSecretData: {
_id: "",
identityUniversalAuth: "",
isClientSecretRevoked: false,
description: "",
clientSecretPrefix: "abc",
clientSecretNumUses: 0,
clientSecretNumUsesLimit: 0,
clientSecretTTL: 0,
createdAt: "2023-01-13T14:16:12.210Z",
updatedAt: "2023-01-13T14:16:12.210Z"
},
Membership: {
user: {
_id: "",
@ -79,6 +114,25 @@ const generateOpenAPISpec = async () => {
role: "owner",
status: "accepted"
},
IdentityMembership: {
identity: {
_id: "",
name: "Machine 1",
authMethod: "universal-auth"
},
workspace: "",
role: "member"
},
IdentityMembershipOrg: {
identity: {
_id: "",
name: "Machine 1",
authMethod: "universal-auth"
},
organization: "",
role: "member",
status: "accepted"
},
Organization: {
_id: "",
name: "Acme Corp.",

@ -1,4 +0,0 @@
FROM alpine
RUN apk add --no-cache tini
COPY infisical /bin/infisical
ENTRYPOINT ["/sbin/tini", "--", "/bin/infisical"]

9
cli/docker/alpine Normal file

@ -0,0 +1,9 @@
FROM alpine
RUN apk add --no-cache tini
## Upgrade OpenSSL libraries to mitigate known vulnerabilities as the current Alpine image has not been patched yet.
RUN apk update && apk upgrade --no-cache libcrypto3 libssl3
COPY infisical /bin/infisical
ENTRYPOINT ["/sbin/tini", "--", "/bin/infisical"]

@ -474,6 +474,7 @@ func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Reques
SetBody(request).
SetQueryParam("workspaceId", request.WorkspaceId).
SetQueryParam("environment", request.Environment).
SetQueryParam("secretPath", request.SecretPath).
SetQueryParam("include_imports", "false").
Get(fmt.Sprintf("%v/v3/secrets/raw", config.INFISICAL_URL))
@ -481,12 +482,12 @@ func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Reques
return GetRawSecretsV3Response{}, fmt.Errorf("CallGetRawSecretsV3: Unable to complete api request [err=%w]", err)
}
if response.IsError() && strings.Contains(response.String(), "Failed to find bot key") {
if response.IsError() && strings.Contains(response.String(), "bot_not_found_error") {
return GetRawSecretsV3Response{}, fmt.Errorf("project with id %s is a legacy project type, please navigate to project settings and disable end to end encryption then try again", request.WorkspaceId)
}
if response.IsError() {
return GetRawSecretsV3Response{}, fmt.Errorf("CallUniversalAuthLogin: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
return GetRawSecretsV3Response{}, fmt.Errorf("CallGetRawSecretsV3: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
}
return getRawSecretsV3Response, nil

@ -5,10 +5,12 @@ package cmd
import (
"bytes"
"encoding/base64"
"fmt"
"io/ioutil"
"os"
"os/signal"
"path"
"strings"
"sync"
"syscall"
@ -36,7 +38,8 @@ type Config struct {
}
type InfisicalConfig struct {
Address string `yaml:"address"`
Address string `yaml:"address"`
ExitAfterAuth bool `yaml:"exit-after-auth"`
}
type AuthConfig struct {
@ -65,8 +68,9 @@ type SinkDetails struct {
}
type Template struct {
SourcePath string `yaml:"source-path"`
DestinationPath string `yaml:"destination-path"`
SourcePath string `yaml:"source-path"`
Base64TemplateContent string `yaml:"base64-template-content"`
DestinationPath string `yaml:"destination-path"`
}
func ReadFile(filePath string) ([]byte, error) {
@ -106,12 +110,7 @@ func appendAPIEndpoint(address string) string {
return address + "/api"
}
func ParseAgentConfig(filePath string) (*Config, error) {
data, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err
}
func ParseAgentConfig(configFile []byte) (*Config, error) {
var rawConfig struct {
Infisical InfisicalConfig `yaml:"infisical"`
Auth struct {
@ -122,7 +121,7 @@ func ParseAgentConfig(filePath string) (*Config, error) {
Templates []Template `yaml:"templates"`
}
if err := yaml.Unmarshal(data, &rawConfig); err != nil {
if err := yaml.Unmarshal(configFile, &rawConfig); err != nil {
return nil, err
}
@ -189,7 +188,38 @@ func ProcessTemplate(templatePath string, data interface{}, accessToken string)
"secret": secretFunction,
}
tmpl, err := template.New(templatePath).Funcs(funcs).ParseFiles(templatePath)
templateName := path.Base(templatePath)
tmpl, err := template.New(templateName).Funcs(funcs).ParseFiles(templatePath)
if err != nil {
return nil, err
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
return nil, err
}
return &buf, nil
}
func ProcessBase64Template(encodedTemplate string, data interface{}, accessToken string) (*bytes.Buffer, error) {
// custom template function to fetch secrets from Infisical
decoded, err := base64.StdEncoding.DecodeString(encodedTemplate)
if err != nil {
return nil, err
}
templateString := string(decoded)
secretFunction := secretTemplateFunction(accessToken)
funcs := template.FuncMap{
"secret": secretFunction,
}
templateName := "base64Template"
tmpl, err := template.New(templateName).Funcs(funcs).Parse(templateString)
if err != nil {
return nil, err
}
@ -216,10 +246,11 @@ type TokenManager struct {
newAccessTokenNotificationChan chan bool
removeClientSecretOnRead bool
cachedClientSecret string
exitAfterAuth bool
}
func NewTokenManager(fileDeposits []Sink, templates []Template, clientIdPath string, clientSecretPath string, newAccessTokenNotificationChan chan bool, removeClientSecretOnRead bool) *TokenManager {
return &TokenManager{filePaths: fileDeposits, templates: templates, clientIdPath: clientIdPath, clientSecretPath: clientSecretPath, newAccessTokenNotificationChan: newAccessTokenNotificationChan, removeClientSecretOnRead: removeClientSecretOnRead}
func NewTokenManager(fileDeposits []Sink, templates []Template, clientIdPath string, clientSecretPath string, newAccessTokenNotificationChan chan bool, removeClientSecretOnRead bool, exitAfterAuth bool) *TokenManager {
return &TokenManager{filePaths: fileDeposits, templates: templates, clientIdPath: clientIdPath, clientSecretPath: clientSecretPath, newAccessTokenNotificationChan: newAccessTokenNotificationChan, removeClientSecretOnRead: removeClientSecretOnRead, exitAfterAuth: exitAfterAuth}
}
func (tm *TokenManager) SetToken(token string, accessTokenTTL time.Duration, accessTokenMaxTTL time.Duration) {
@ -242,18 +273,26 @@ func (tm *TokenManager) GetToken() string {
// Fetches a new access token using client credentials
func (tm *TokenManager) FetchNewAccessToken() error {
clientIDAsByte, err := ReadFile(tm.clientIdPath)
if err != nil {
return fmt.Errorf("unable to read client id from file path '%s' due to error: %v", tm.clientIdPath, err)
clientID := os.Getenv("INFISICAL_UNIVERSAL_AUTH_CLIENT_ID")
if clientID == "" {
clientIDAsByte, err := ReadFile(tm.clientIdPath)
if err != nil {
return fmt.Errorf("unable to read client id from file path '%s' due to error: %v", tm.clientIdPath, err)
}
clientID = string(clientIDAsByte)
}
clientSecretAsByte, err := ReadFile(tm.clientSecretPath)
if err != nil {
if len(tm.cachedClientSecret) == 0 {
return fmt.Errorf("unable to read client secret from file and no cached client secret found: %v", err)
} else {
clientSecretAsByte = []byte(tm.cachedClientSecret)
clientSecret := os.Getenv("INFISICAL_UNIVERSAL_CLIENT_SECRET")
if clientSecret == "" {
clientSecretAsByte, err := ReadFile(tm.clientSecretPath)
if err != nil {
if len(tm.cachedClientSecret) == 0 {
return fmt.Errorf("unable to read client secret from file and no cached client secret found: %v", err)
} else {
clientSecretAsByte = []byte(tm.cachedClientSecret)
}
}
clientSecret = string(clientSecretAsByte)
}
// remove client secret after first read
@ -261,13 +300,10 @@ func (tm *TokenManager) FetchNewAccessToken() error {
os.Remove(tm.clientSecretPath)
}
clientId := string(clientIDAsByte)
clientSecret := string(clientSecretAsByte)
// save as cache in memory
tm.cachedClientSecret = clientSecret
err, loginResponse := universalAuthLogin(clientId, clientSecret)
err, loginResponse := universalAuthLogin(clientID, clientSecret)
if err != nil {
return err
}
@ -351,6 +387,11 @@ func (tm *TokenManager) ManageTokenLifecycle() {
}
}
if tm.exitAfterAuth {
time.Sleep(25 * time.Second)
os.Exit(0)
}
if accessTokenRefreshedTime.IsZero() {
accessTokenRefreshedTime = tm.accessTokenFetchedTime
} else {
@ -393,20 +434,23 @@ func (tm *TokenManager) FetchSecrets() {
token := tm.GetToken()
if token != "" {
for _, secretTemplate := range tm.templates {
processedTemplate, err := ProcessTemplate(secretTemplate.SourcePath, nil, token)
if err != nil {
log.Error().Msgf("template engine: unable to render secrets because %s. Will try again in 30 seconds", err)
var processedTemplate *bytes.Buffer
var err error
if secretTemplate.SourcePath != "" {
processedTemplate, err = ProcessTemplate(secretTemplate.SourcePath, nil, token)
} else {
processedTemplate, err = ProcessBase64Template(secretTemplate.Base64TemplateContent, nil, token)
}
if err != nil {
log.Error().Msgf("template engine: unable to render secrets because %s. Will try again on next cycle", err)
// wait a bit before trying again
time.Sleep((30 * time.Second))
continue
}
if err := WriteBytesToFile(processedTemplate, secretTemplate.DestinationPath); err != nil {
log.Error().Msgf("template engine: unable to write secrets to path because %s. Will try again in 30 seconds", err)
log.Error().Msgf("template engine: unable to write secrets to path because %s. Will try again on next cycle", err)
// wait a bit before trying again
time.Sleep((30 * time.Second))
continue
}
@ -450,12 +494,37 @@ var agentCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag config")
}
if !FileExists(configPath) {
log.Error().Msgf("Unable to locate %s. The provided agent config file path is either missing or incorrect", configPath)
var agentConfigInBytes []byte
agentConfigInBase64 := os.Getenv("INFISICAL_AGENT_CONFIG_BASE64")
if agentConfigInBase64 == "" {
data, err := ioutil.ReadFile(configPath)
if err != nil {
if !FileExists(configPath) {
log.Error().Msgf("Unable to locate %s. The provided agent config file path is either missing or incorrect", configPath)
return
}
}
agentConfigInBytes = data
}
if agentConfigInBase64 != "" {
decodedAgentConfig, err := base64.StdEncoding.DecodeString(agentConfigInBase64)
if err != nil {
log.Error().Msgf("Unable to decode base64 config file because %v", err)
return
}
agentConfigInBytes = decodedAgentConfig
}
if !FileExists(configPath) && agentConfigInBase64 == "" {
log.Error().Msgf("No agent config file provided. Please provide a agent config file", configPath)
return
}
agentConfig, err := ParseAgentConfig(configPath)
agentConfig, err := ParseAgentConfig(agentConfigInBytes)
if err != nil {
log.Error().Msgf("Unable to prase %s because %v. Please ensure that is follows the Infisical Agent config structure", configPath, err)
return
@ -472,7 +541,7 @@ var agentCmd = &cobra.Command{
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
filePaths := agentConfig.Sinks
tm := NewTokenManager(filePaths, agentConfig.Templates, configUniversalAuthType.ClientIDPath, configUniversalAuthType.ClientSecretPath, tokenRefreshNotifier, configUniversalAuthType.RemoveClientSecretOnRead)
tm := NewTokenManager(filePaths, agentConfig.Templates, configUniversalAuthType.ClientIDPath, configUniversalAuthType.ClientSecretPath, tokenRefreshNotifier, configUniversalAuthType.RemoveClientSecretOnRead, agentConfig.Infisical.ExitAfterAuth)
go tm.ManageTokenLifecycle()
go tm.FetchSecrets()

@ -11,7 +11,6 @@ import (
"github.com/Infisical/infisical-merge/packages/models"
"github.com/Infisical/infisical-merge/packages/util"
"github.com/posthog/posthog-go"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
@ -102,7 +101,7 @@ var exportCmd = &cobra.Command{
fmt.Print(output)
Telemetry.CaptureEvent("cli-command:export", posthog.NewProperties().Set("secretsCount", len(secrets)).Set("version", util.CLI_VERSION))
// Telemetry.CaptureEvent("cli-command:export", posthog.NewProperties().Set("secretsCount", len(secrets)).Set("version", util.CLI_VERSION))
},
}

@ -168,7 +168,7 @@ func GetPlainTextSecretsViaMachineIdentity(accessToken string, workspaceId strin
getSecretsRequest.SecretPath = secretsPath
}
rawSecrets, err := api.CallGetRawSecretsV3(httpClient, api.GetRawSecretsV3Request{WorkspaceId: workspaceId, SecretPath: environmentName, Environment: environmentName})
rawSecrets, err := api.CallGetRawSecretsV3(httpClient, api.GetRawSecretsV3Request{WorkspaceId: workspaceId, SecretPath: secretsPath, Environment: environmentName})
if err != nil {
return nil, err
}

@ -1,4 +1,4 @@
---
title: "Create"
openapi: "POST /api/v1/workspace/{workspaceId}/environments"
openapi: "POST /api/v2/workspace/{workspaceId}/environments"
---

@ -0,0 +1,4 @@
---
title: "Create"
openapi: "POST /api/v1/identities/"
---

@ -0,0 +1,4 @@
---
title: "Delete"
openapi: "DELETE /api/v1/identities/{identityId}"
---

@ -0,0 +1,4 @@
---
title: "Update"
openapi: "PATCH /api/v1/identities/{identityId}"
---

@ -1,4 +1,4 @@
---
title: "Delete Membership"
title: "Delete User Membership"
openapi: "DELETE /api/v2/organizations/{organizationId}/memberships/{membershipId}"
---

@ -0,0 +1,4 @@
---
title: "List Identity Memberships"
openapi: "GET /api/v2/organizations/{organizationId}/identity-memberships"
---

@ -1,4 +1,4 @@
---
title: "Get Memberships"
title: "Get User Memberships"
openapi: "GET /api/v2/organizations/{organizationId}/memberships"
---

@ -1,4 +1,4 @@
---
title: "Update Membership"
title: "Update User Membership"
openapi: "PATCH /api/v2/organizations/{organizationId}/memberships/{membershipId}"
---

@ -1,4 +1,4 @@
---
title: "Get Projects"
openapi: "GET /api/v2/organizations/{organizationId}/workspaces"
---
---

@ -2,3 +2,9 @@
title: "Get"
openapi: "GET /api/v2/service-token/"
---
<Warning>
This endpoint will be deprecated in the near future with the removal of service tokens in Q1/Q2 2024.
We recommend switching to using [identities](/documentation/platform/identities/overview) if your client supports it.
</Warning>

@ -0,0 +1,4 @@
---
title: "Attach"
openapi: "POST /api/v1/auth/universal-auth/identities/{identityId}"
---

@ -0,0 +1,4 @@
---
title: "Create Client Secret"
openapi: "POST /api/v1/auth/universal-auth/identities/{identityId}/client-secrets"
---

@ -0,0 +1,4 @@
---
title: "List Client Secrets"
openapi: "GET /api/v1/auth/universal-auth/identities/{identityId}/client-secrets"
---

@ -0,0 +1,4 @@
---
title: "Login"
openapi: "POST /api/v1/auth/universal-auth/login"
---

@ -0,0 +1,4 @@
---
title: "Renew Access Token"
openapi: "POST /api/v1/auth/token/renew"
---

@ -0,0 +1,4 @@
---
title: "Retrieve"
openapi: "GET /api/v1/auth/universal-auth/identities/{identityId}"
---

@ -0,0 +1,4 @@
---
title: "Revoke Client Secret"
openapi: "POST /api/v1/auth/universal-auth/identities/{identityId}/client-secrets/{clientSecretId}/revoke"
---

@ -0,0 +1,4 @@
---
title: "Update"
openapi: "PATCH /api/v1/auth/universal-auth/identities/{identityId}"
---

@ -2,3 +2,9 @@
title: "Get My User"
openapi: "GET /api/v2/users/me"
---
<Warning>
This endpoint will be deprecated in the near future in Q1/Q2 2024.
We recommend switching to using [identities](/documentation/platform/identities/overview).
</Warning>

@ -2,3 +2,9 @@
title: "Get My Organizations"
openapi: "GET /api/v2/users/me/organizations"
---
<Warning>
This endpoint will be deprecated in the near future in Q1/Q2 2024.
We recommend switching to using [identities](/documentation/platform/identities/overview).
</Warning>

@ -0,0 +1,4 @@
---
title: "Delete Identity Membership"
openapi: "DELETE /api/v2/workspace/{workspaceId}/identity-memberships/{identityId}"
---

@ -1,4 +1,4 @@
---
title: "Delete Membership"
title: "Delete User Membership"
openapi: "DELETE /api/v2/workspace/{workspaceId}/memberships/{membershipId}"
---

@ -0,0 +1,4 @@
---
title: "List Identity Memberships"
openapi: "GET /api/v2/workspace/{workspaceId}/identity-memberships"
---

@ -1,4 +1,4 @@
---
title: "Get Memberships"
title: "Get User Memberships"
openapi: "GET /api/v2/workspace/{workspaceId}/memberships"
---

@ -0,0 +1,4 @@
---
title: "Update Identity Membership"
openapi: "PATCH /api/v2/workspace/{workspaceId}/identity-memberships/{identityId}"
---

@ -1,4 +1,4 @@
---
title: "Update Membership"
title: "Update User Membership"
openapi: "PATCH /api/v2/workspace/{workspaceId}/memberships/{membershipId}"
---

@ -2,3 +2,8 @@
title: "Get Key"
openapi: "GET /api/v2/workspace/{workspaceId}/encrypted-key"
---
<Warning>
This endpoint will be deprecated in the near future in Q1/Q2 2024.
We recommend using Infisical in non-E2EE mode going forward.
</Warning>

@ -3,29 +3,34 @@ title: "Authentication"
description: "How to authenticate with the Infisical Public API"
---
The Public API accepts multiple modes of authentication being via [Infisical Token](/documentation/platform/token) or API Key.
You can authenticate with the Infisical API using [Identities](/documentation/platform/identities/overview) paired with authentication modes such as [Universal Auth](/documentation/platform/identities/universal-auth).
- [Infisical Token](/documentation/platform/token): Provides short-lived, scoped CRUD access to the secrets of a specific project and environment.
- API Key: Provides full access to all endpoints representing the user without ability to encrypt/decrypt secrets for **E2EE** endpoints.
To interact with the Infisical API, you will need to obtain an access token. Follow the step by [step guide](/documentation/platform/identities/universal-auth) to get an access token via Universal Auth.
<Tabs>
<Tab title="Infisical Token">
The Infisical Token mode uses an Infisical Token to authenticate with the API.
To authenticate requests with Infisical using the Infisical Token, you must include your Infisical Token in the `Authorization` header of HTTP requests made to the platform with the value `Bearer <infisical_token>`.
**FAQ**
You can obtain an Infisical Token in Project Settings > Service Tokens.
<AccordionGroup>
<Accordion title="What happened to the Service Token and API Key authentication modes?">
The Service Token and API Key authentication modes are being deprecated out in favor of [Identities](/documentation/platform/identity).
We expect to make a deprecation notice in the coming months alongside a larger deprecation initiative planned for Q1/Q2 2024.
![token add](../../images/project-token-add.png)
</Tab>
<Tab title="API Key">
The API key mode uses an API key to authenticate with the API.
To authenticate requests with Infisical using the API Key, you must include an API key in the `X-API-KEY` header of HTTP requests made to the platform.
You can obtain an API key in User Settings > API Keys
![API key dashboard](../../images/api-key-dashboard.png)
![API key in personal settings](../../images/api-key-settings.png)
</Tab>
</Tabs>
With identities, we're improving significantly over the shortcomings of Service Tokens and API Keys. Amongst many differences, identities provide broader access over the Infisical API, utilizes the same role-based
permission system used by users, and comes with ample more configurable security measures.
</Accordion>
<Accordion title="Why can I not create, read, update, or delete an identity?">
There are a few reasons for why this might happen:
- You have insufficient organization permissions to create, read, update, delete identities.
- The identity you are trying to read, update, or delete is more privileged than yourself.
- The role you are trying to create an identity for or update an identity to is more privileged than yours.
</Accordion>
<Accordion title="Why is the Infisical API rejecting my identity credentials?">
There are a few reasons for why this might happen:
- The client secret or access token has expired.
- The identity is insufficently permissioned to interact with the resources you wish to access.
- You are attempting to access a `/raw` secrets endpoint that requires your project to disable E2EE.
- The client secret/access token is being used from an untrusted IP.
</Accordion>
</AccordionGroup>

@ -1,18 +0,0 @@
---
title: "Blind Indices"
---
In April 2023, we added the capability for users to query for secrets by name to improve the user experience of Infisical. Previously, it was only possible to query by id of the secret or fetch all secrets belonging to a project and environment.
Blind indexing must be enabled for projects created prior to April 2023 to take effect. If your project can be blind indexed, then you'll see a section in your project settings appear as shown below:
![project enable blind indices](../../images/project-settings-blind-indices.png)
It works using virtually irreversible blind indices generated by applying `argon2id` to the name of each secret and a random 128-bit salt assigned to each project on the server. We continue to keep the values of secrets E2EE by default.
You can read more about it [here](/security/mechanics).
<Note>
As previously mentioned, all projects made after April 2023 are automatically blind indexed. If you created a project before this date, you have to enable it manually in your project settings.
</Note>

@ -2,6 +2,10 @@
title: "E2EE Enabled"
---
<Note>
E2EE enabled mode only works with [Service Tokens](/documentation/platform/token) and cannot be used with [Identities](/documentation/platform/identities/overview).
</Note>
Using Infisical's API to read/write secrets with E2EE enabled allows you to create, update, and retrieve secrets
but requires you to perform client-side encryption/decryption operations. For this reason, we recommend using one of the available
SDKs instead.

@ -6,8 +6,4 @@ Infisical's Public (REST) API provides users an alternative way to programmatica
secrets via HTTPS requests. This can be useful for automating tasks, such as
rotating credentials, or for integrating secret management into a larger system.
With the Public API, users can create, read, update, and delete secrets, as well as manage access control, query audit logs, and more.
<Warning>
In April 2023, we added the capability for users to query for secrets by name to improve the user experience of Infisical. If your project was created prior to April 2023, please read and follow the section on [blind indices](./blind-indices) and how to enable them for better usage of Infisical.
</Warning>
With the Public API, you can create, read, update, and delete secrets, as well as manage access control, query audit logs, and more.

@ -6,6 +6,9 @@ The changelog below reflects new product developments and updates on a monthly b
## December 2023
- Released [(machine) identities](https://infisical.com/docs/documentation/platform/identities/overview) and [universal auth](https://infisical.com/docs/documentation/platform/identities/universal-auth) features.
- Created new cross-language SDKs for [Python](https://infisical.com/docs/sdks/languages/python), [Node](https://infisical.com/docs/sdks/languages/node), and [Java](https://infisical.com/docs/sdks/languages/java).
- Released first version of the [Infisical Agent](https://infisical.com/docs/infisical-agent/overview)
- Added ability to [manage folders via CLI](https://infisical.com/docs/cli/commands/secrets).
## November 2023

@ -6,7 +6,7 @@ description: "Frequently Asked Questions about contributing to Infisical"
Frequently asked questions about contributing to Infisical can be found on this page.
If you can't find the answer you are looking for, please create an issue on our GitHub repository or join our Slack channel for additional support.
<Accordion title="Error building backend (Alpine Linux CDN temporary error)">
<Accordion title="Error building Infisical platform backend (Alpine Linux CDN temporary error)">
The Alpine Linux CDN may be unavailable/down in your region infrequently (eg. there is an unplanned outage). One possible fix is to add a retry mechanism and a fallback mirrors array to the Dockerfile. You can also use this as an opportunity to pin the Alpine Linux version for Docker to use in case there are issues with the latest version. Ensure to use https for the mirrors.
#### Make the following changes to the backend Dockerfile

@ -1,14 +1,30 @@
---
title: "Overview"
description: "We welcome any contributions to Infisical, big or small."
description: "Contributing to the Infisical ecosystem."
---
To set a strong foundation, this section outlines how we, the community and members of Infisical,
should approach the development and contribution process.
## Code-bases
Infisical has two major code-bases. One for the platform code, and one for SDKs. The contribution process has some key differences between the two, so we've split the documentation into two sections:
- The [Infisical Platform](https://github.com/Infisical/infisical), the Infisical platform itself.
- The [Infisical SDK](https://github.com/Infisical/sdk), the official Infisical client SDKs.
<CardGroup cols={2}>
<Card title="Infisical Platform" href="/contributing/platform/developing" icon="layer-group" color="#A1B659">
The Infisical platform is the core of the Infisical ecosystem.
</Card>
<Card href="/contributing/sdk/developing" title="Infisical SDK" icon="code" color="#A1B659">
The SDKs are the official Infisical client libraries, used by developers to easily interact with the Infisical platform.
</Card>
</CardGroup>
## Community
We are building an inclusive community, and this means adhering to the [Code of Conduct](/contributing/code-of-conduct).
We are building an inclusive community, and this means adhering to the [Code of Conduct](/contributing/getting-started/code-of-conduct).
## Bugs and issues
@ -29,8 +45,10 @@ If you're ever in doubt about whether or not a proposed feature aligns with Infi
## Writing and submitting code
Anyone can contribute code to Infisical. To get started, check out the [local development guide](/contributing/developing), make your changes, and submit a pull request to the main repository
adhering to the [pull request guide](/contributing/pull-requests).
Anyone can contribute code to Infisical. To get started, check out the local development guides for each language.
- Local development guide for Platform is [here](/contributing/platform/developing).
- Local development guide for SDK is [here](/contributing/sdk/developing).
## Licensing
@ -38,3 +56,4 @@ adhering to the [pull request guide](/contributing/pull-requests).
Most of Infisical's code is under the MIT license, though some paid feature restrictions are covered by a proprietary license.
Any third party components incorporated into our code are licensed under the original license provided by the applicable component owner.

@ -19,7 +19,7 @@ You should follow the automatically-generated PR template to fill in the PR desc
Give a functional overview of how your feature works, including how the user can use the feature. Then share any technical details in an overview of how the PR works.
As of `06-01-2023`, all PRs created after this date are required to attach a video of you performing the described functionality.
As of `06-01-2023`, all PRs created after this date are required to attach a video of you performing the described functionality.
### Bug Fix PRs
@ -34,6 +34,8 @@ Once your PR is reviewed, one or two relevant members of the Infisical team shou
- Vlad: Frontend, Web UI
- Tony: Backend, SDKs, Security
- Maidul: Backend, CI/CD, CLI, Kubernetes Operator
- Daniel: Frontend, UI/UX, Backend, SDKs
The team member(s) will start by enabling baseline checks to ensure that there are no leaked secrets, new dependencies are clear, and the frontend/backend services start up. Afterward, they will review your PR thoroughly by testing the code and leave any feedback or work in with you to revise the PR up to standard.

@ -1,6 +1,6 @@
---
title: 'Local development'
description: 'This guide will help you set up and run Infisical in local development.'
description: 'This guide will help you set up and run the Infisical platform in local development.'
---
## Fork and clone the repo

@ -0,0 +1,408 @@
---
title: "Local development"
description: "This guide will help you contribute to the Infisical SDK."
---
## Fork and clone the repo
[Fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) the [repository](https://github.com/Infisical/sdk) to your own GitHub account and then [clone](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) it to your local device.
Once, you've done that, create a new branch:
```console
git checkout -b MY_BRANCH_NAME
```
## Set up environment variables
Start by creating a .env file at the root of the Infisical directory then copy the contents of the file below into the .env file.
<Accordion title=".env file content">
```env
# This is required for running tests locally.
# Rename this file to ".env" and fill in the values below.
# Please make sure that the machine identity has access to the project you are testing in.
# https://infisical.com/docs/documentation/platform/identities/universal-auth
INFISICAL_UNIVERSAL_CLIENT_ID=MACHINE_IDENTITY_CLIENT_ID
INFISICAL_UNIVERSAL_CLIENT_SECRET=MACHINE_IDENTITY_CLIENT_SECRET
# The ID of the Infisical project where we will create the test secrets.
# NOTE: The project must have a dev environment. (This is created by default when you create a project.)
INFISICAL_PROJECT_ID=INFISICAL_TEST_PROJECT_ID
# The Infisical site URL. If you are testing with a local Infisical instance, then this should be set to "http://localhost:8080".
INFISICAL_SITE_URL=https://app.infisical.com
````
</Accordion>
<Warning>
The above values are required for running tests locally. Before opening a pull request, make sure to run `cargo test` to ensure that all tests pass.
</Warning>
## Guidelines
### Predictable and consistent
When adding new functionality (such as new functions), it's very important that the functionality is added to _all_ the SDK's. This is to ensure that the SDK's are predictable and consistent across all languages. If you are adding new functionality, please make sure to add it to all the SDK's.
### Handling errors
Error handling is very important when writing SDK's. We want to make sure that the SDK's are easy to use, and that the user gets a good understanding of what went wrong when something fails. When adding new functionality, please make sure to add proper error handling. [Read more about error handling here](#error-handling).
### Tests
If you add new functionality or modify existing functionality, please write tests thats properly cover the new functionality. You can run tests locally by running `cargo test` from the root directory. You must always run tests before opening a pull request.
### Code style
Please follow the default rust styling guide when writing code for the base SDK. [Read more about rust code style here](https://doc.rust-lang.org/nightly/style-guide/#the-default-rust-style).
## Prerequisites for contributing
### Understanding the terms
In the guide we use some terms that might be unfamiliar to you. Here's a quick explanation of the terms we use:
- **Base SDK**: The base SDK is the SDK that all other SDK's are built on top of. The base SDK is written in Rust, and is responsible for executing commands and parsing the input and output to and from JSON.
- **Commands**: Commands are what's being sent from the target language to the command handler. The command handler uses the command to execute the corresponding function in the base SDK. Commands are in reality just a JSON string that tells the command handler what function to execute, and what input to use.
- **Command handler**: The command handler is the part of the base SDK that takes care of executing commands. It also takes care of parsing the input and output to and from JSON.
- **Target language**: The target language refers to the actual SDK code. For example, the [Node.js SDK](https://www.npmjs.com/package/@infisical/sdk) is a "target language", and so is the [Python SDK](https://pypi.org/project/infisical-python/).
### Understanding the execution flow
After the target language SDK is initiated, it uses language-specific bindings to interact with the base SDK.
These bindings are instantiated, setting up the interface for command execution. A client within the command handler is created, which issues commands to the base SDK.
When a command is executed, it is first validated. If valid, the command handler locates the corresponding command to perform. If the command executes successfully, the command handler returns the output to the target language SDK, where it is parsed and returned to the user.
If the command handler fails to validate the input, an error will be returned to the target language SDK.
<Frame caption="Execution flow diagram for the SDK from the target language to the base SDK. The execution flow is the same for all target languages.">
<img height="640" width="520" src="/images/sdk-flow.png" />
</Frame>
### Rust knowledge
Contributing to the SDK requires intermediate to advanced knowledge of Rust concepts such as lifetimes, traits, generics, and async/await _(futures)_, and more.
### Rust setup
The base SDK is written in rust. Therefore you must have rustc and cargo installed. You can install rustc and cargo by following the instructions [here](https://www.rust-lang.org/tools/install).
You shouldn't have to use the rust cross compilation toolchain, as all compilation is done through a collection of Github Actions. However. If you need to test cross compilation, please do so with Github Actions.
### Tests
If you add new functionality or modify existing functionality, please write tests thats properly cover the new functionality. You can run tests locally by running `cargo test` from the root directory.
### Language-specific crates
The language-specific crates should ideally never have to be modified, as they are simply a wrapper for the `infisical-json` crate, which executes "commands" from the base SDK. If you need to create a new target-language specific crate, please try to create native bindings for the target language. Some languages don't have direct support for native bindings (Java as an example). In those cases we can use the C bindings (`crates/infisical-c`) in the target language.
## Generate types
Having almost seemless type safety from the base SDK to the target language is critical, as writing types for each language has a lot of drawbacks such as duplicated code, and lots of overhead trying to keep the types up-to-date and in sync across a large collection of languages. Therefore we decided to use [QuickType](https://quicktype.io/) and [Serde](https://serde.rs/) to help us generate types for each language. In our Rust base SDK (`crates/infisical`), we define all the inputs/outputs.
If you are interested in reading about QuickType works under the hood, you can [read more here](http://blog.quicktype.io/under-the-hood/).
This is an example of a type defined in Rust (both input and output). For this to become a generated type, you'll need to add it to our schema generator. More on that further down.
```rust
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase")]
// Input:
pub struct CreateSecretOptions {
pub environment: String, // environment
pub secret_comment: Option<String>, // secretComment
pub path: Option<String>, // secretPath
pub secret_value: String, // secretValue
pub skip_multiline_encoding: Option<bool>, // skipMultilineEncoding
pub r#type: Option<String>, // shared / personal
pub project_id: String, // workspaceId
pub secret_name: String, // secretName (PASSED AS PARAMETER IN REQUEST)
}
// Output:
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateSecretResponse {
pub secret: Secret, // "Secret" is defined elsewhere.
}
````
### Adding input types to the schema generator
You will _only_ have to define outputs in our schema generator, then QuickType will take care of the rest behind the scenes. You can find the Rust crate that takes care of type generation here: `crates/sdk-schemas/src/main.rs`.
Simply add the output _(also called response)_, to the `write_schema_for_response!` macro. This will let QuickType know that it should generate types for the given structs. The main function will look something like this:
```rust
fn main() -> Result<()> {
// Input types for new Client
write_schema_for!(infisical_json::client::ClientSettings);
// Input types for Client::run_command
write_schema_for!(infisical_json::command::Command);
// Output types for Client::run_command
// Only add structs which are direct results of SDK commands.
write_schema_for_response! {
infisical::manager::secrets::GetSecretResponse,
infisical::manager::secrets::ListSecretsResponse,
infisical::manager::secrets::UpdateSecretResponse,
infisical::manager::secrets::DeleteSecretResponse,
infisical::manager::secrets::CreateSecretResponse, // <-- This is the output from the above example!
infisical::auth::AccessTokenSuccessResponse
};
Ok(())
}
```
### Generating the types for the target language
Once you've added the output to the schema generator, you can generate the types for the target language by running the following command from the root directory:
```console
$ npm install
$ npm run schemas
```
<Warning>If you change any of the structs defined in the base SDK, you will need to run this script to re-generate the types.</Warning>
This command will run the `schemas.ts` file found in the `support/scripts` folder. If you are adding a new language, it's important that you add the language to the code.
This is an example of how how we generate types for Node.js:
```ts
const ts = await quicktype({
inputData,
lang: "typescript",
rendererOptions: {}
});
await ensureDir("./languages/node/src/infisical_client");
writeToFile("./languages/node/src/infisical_client/schemas.ts", ts.lines);
```
## Building bindings
We've tried to streamline the building process as much as possible. So you shouldn't have to worry much about building bindings, as it should just be a few commands.
### Node.js
Building bindings for Node.js is very straight foward. The command below will generate NAPI bindings for Node.js, and move the bindings to the correct folder. We use [NAPI-RS](https://napi.rs/) to generate the bindings.
```console
$ cd languages/node
$ npm run build
```
### Python
To generate and use python bindings you will need to run the following commands.
The Python SDK is located inside the crates folder. This is a limitation of the maturin tool, forcing us to structure the project in this way.
```console
$ pip install -U pip maturin
$ cd crates/infisical-py
$ python3 -m venv .venv
$ source .venv/bin/activate
$ maturin develop
```
<Warning>
After running the commands above, it's very important that you rename the generated .so file to `infisical_py.so`. After renaming it you also need to move it into the root of the `crates/infisical-py` folder.
</Warning>
### Java
Java uses the C bindings to interact with the base SDK. To build and use the C bindings in Java, please follow the instructions below.
```console
$ cd crates/infisical-c
$ cargo build --release
$ cd ../../languages/java
```
<Warning>
After generating the C bindings, the generated .so or .dll has been created in the `/target` directory at the root of the project.
You have to manually move the generated file into the `languages/java/src/main/resources` directory.
</Warning>
## Error handling
### Error handling in the base SDK
The base SDK should never panic. If an error occurs, we should return a `Result` with an error message. We have a custom Result type defined in the `error.rs` file in the base SDK.
All our errors are defined in an enum called `Error`. The `Error` enum is defined in the `error.rs` file in the base SDK. The `Error` enum is used in the `Result` type, which is used as the return type for all functions in the base SDK.
```rust
#[derive(Debug, Error)]
pub enum Error {
// Secret not found
#[error("Secret with name '{}' not found.", .secret_name)]
SecretNotFound { secret_name: String },
// .. other errors
// Errors that are not specific to the base SDK.
#[error(transparent)]
Reqwest(#[from] reqwest::Error),
#[error(transparent)]
Serde(#[from] serde_json::Error),
#[error(transparent)]
Io(#[from] std::io::Error),
}
```
### Returning an error
You can find many examples of how we return errors in the SDK code. A relevant example is for creating secrets, which can be found in `crates/infisical/src/api/secrets/create_secret.rs`. When the error happened due to a request error to our API, we have an API error handler. This prevents duplicate code and keeps error handling consistent across the SDK. You can find the api error handler in the `error.rs` file.
### Error handling in the target language SDK's.
All data sent to the target language SDK has the same format. The format is an object with 3 fields: `success (boolean)`, `data (could be anything or nothing)`, and `errorMessage (string or null)`.
The `success` field is used to determine if the request was successful or not. The `data` field is used to return data from the SDK. The `errorMessage` field is used to return an error message if the request was not successful.
This means that if the success if false or if the error message is not null, something went wrong and we should throw an error on the target-language level, with the error message.
## Command handler
### What is the command handler
The command handler (the `infisical-json` crate), takes care of executing commands sent from the target language. It also takes care of parsing the input and output to and from JSON. The command handler is the only part of the base SDK that should be aware of JSON. The rest of the base SDK should be completely unaware of JSON, and only work with the Rust structs defined in the base SDK.
The command handler exposes a function called `run_command`, which is what we use in the target language to execute commands. The function takes a json string as input, and returns a json string as output. We use helper functions generated by QuickType to convert the input and output to and from JSON.
### Creating new SDK methods
Creating new commands is necessary when adding new methods to the SDK's. Defining a new command is a 3-step process in most cases.
#### 1. Define the input and output structs
Earlier in this guide, we defined the input and output structs for the `CreateSecret` command. We will use that as an example here as well.
#### 2. Creating the method in the base SDK
The first step is to create the method in the base SDK. This step will be different depending on what method you are adding. In this example we're going to assume you're adding a function for creating a new secret.
After you created the function for creating the secret, you'll need need to add it to the ClientSecrets implementation. We do it this way to keep the code organized and easy to read. The ClientSecrets struct is located in the `crates/infisical/src/manager/secrets.rs` file.
```rust
pub struct ClientSecrets<'a> {
pub(crate) client: &'a mut crate::Client,
}
impl<'a> ClientSecrets<'a> {
pub async fn create(&mut self, input: &CreateSecretOptions) -> Result<CreateSecretResponse> {
create_secret(self.client, input).await // <-- This is the function you created!
}
}
impl<'a> Client {
pub fn secrets(&'a mut self) -> ClientSecrets<'a> {
ClientSecrets { client: self }
}
}
```
#### 3. Define a new command
We define new commands in the `crates/infisical-json/src/command.rs` file. The `Command` enum is what we use to define new commands.
In the codesnippet below we define a new command called `CreateSecret`. The `CreateSecret` command takes a `CreateSecretOptions` struct as input. We don't have to define the output, because QuickType's converter helps us with figuring out the return type for each command.
````rust
```rust
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub enum Command {
GetSecret(GetSecretOptions),
ListSecrets(ListSecretsOptions),
CreateSecret(CreateSecretOptions), // <-- The new command!
UpdateSecret(UpdateSecretOptions),
DeleteSecret(DeleteSecretOptions),
}
````
#### 4. Add the command to the command handler
After defining the command, we need to add it to the command handler itself. This takes place in the `crates/infisical-json/src/client.rs` file. The `run_command` function is what we use to execute commands.
In the Client implementation we try to parse the JSON string into a `Command` enum. If the parsing is successful, we match the command and execute the corresponding function.
```rust
match cmd {
Command::GetSecret(req) => self.0.secrets().get(&req).await.into_string(),
Command::ListSecrets(req) => self.0.secrets().list(&req).await.into_string(),
Command::UpdateSecret(req) => self.0.secrets().update(&req).await.into_string(),
Command::DeleteSecret(req) => self.0.secrets().delete(&req).await.into_string(),
// This is the new command:
Command::CreateSecret(req) => self.0.secrets().create(&req).await.into_string(),
}
```
#### 5. Implementing the new command in the target language SDK's
We did it! We've now added a new command to the base SDK. The last step is to implement the new command in the target language SDK's. The process is a little different from language to language, but in this example we're going to assume that we're adding a new command to the Node.js SDK.
First you'll need to generate the new type schemas, we added a new command, input struct, and output struct. [Read more about generating types here](#generating-the-types-for-the-target-language).
Secondly you need to build the new node bindings so we can use the new functionality in the Node.js SDK. You can do this by running the following command from the `languages/node` directory:
```console
$ npm install
$ npm run build
```
The build command will execute a build script in the `infisical-napi` crate, and move the generated bindings to the appropriate folder.
After building the new bindings, you can access the new functionality in the Node.js SDK source.
```ts
// 'binding' is a js file that makes it easier to access the methods in the bindings. (it's auto generated when running npm run build)
import * as rust from "../../binding";
// We can import the newly generated types from the schemas.ts file. (Generated with QuickType!)
import type { CreateSecretOptions, CreateSecretResponse } from "./schemas";
// This is the QuickType converter that we use to create commands with! It takes care of all JSON parsing and serialization.
import { Convert, ClientSettings } from "./schemas";
export class InfisicalClient {
#client: rust.Client;
constructor(settings: ClientSettings) {
const settingsJson = settings == null ? null : Convert.clientSettingsToJson(settings);
this.#client = new rust.InfisicalClient(settingsJson);
}
// ... getSecret
// ... listSecrets
// ... updateSecret
// ... deleteSecret
async createSecret(options: CreateSecretOptions): Promise<CreateSecretResponse["secret"]> {
// The runCommand will return a JSON string, which we can parse into a CreateSecretResponse.
const command = await this.#client.runCommand(
Convert.commandToJson({
createSecret: options
})
);
const response = Convert.toResponseForCreateSecretResponse(command); // <-- This is the QuickType converter in action!
// If the response is not successful or the data is null, we throw an error.
if (!response.success || response.data == null) {
throw new Error(response.errorMessage ?? "Something went wrong");
}
// To make it easier to work with the response, we return the secret directly.
return response.data.secret;
}
}
```
And that's it! We've now added a new command to the base SDK, and implemented it in the Node.js SDK. The process is very similar for all other languages, but the code will look a little different.
## Conclusion
The SDK has a lot of moving parts, and it can be a little overwhelming at first. But once you get the hang of it, it's actually quite simple. If you have any questions, feel free to reach out to us on [Slack](https://infisical.com/slack), or [open an issue](https://github.com/Infisical/sdk/issues) on GitHub.

@ -2,58 +2,127 @@
title: "REST API"
---
Infisical's Public (REST) API is the most flexible, platform-agnostic way to read/write secrets for your application.
Infisical's REST API is the most flexible way to read/write secrets for your application.
Prerequisites:
In this brief, we'll explore how to fetch a secret back from a project on [Infisical Cloud](https://app.infisical.com) via the REST API.
- Have a project with secrets ready in [Infisical Cloud](https://app.infisical.com).
- Create an [Infisical Token](/documentation/platform/token) scoped to an environment in your project in Infisical.
<Steps>
<Step title="Create a project with a secret">
To create a project, head to your Organization Overview and press **Add New Project**; we'll call the project **Demo App**.
![create project](../../images/getting-started/api/org-create-project-1.png)
![create project](../../images/getting-started/api/org-create-project-2.png)
Next, let's head to the **Development** environment of the project and add a secret `FOO=BAR` to it.
![explore project env](../../images/getting-started/api/project-explore-env.png)
![create secret](../../images/getting-started/api/project-create-secret.png)
![project dashboard](../../images/getting-started/api/project-dashboard.png)
To keep it simple, we're going to fetch secrets from the API with **End-to-End Encryption (E2EE)** disabled.
<Note>
For this brief, you'll need to disable end-to-end encryption in your Project Settings
</Note>
</Step>
<Step title="Create an identity">
Next, we need to create an identity to represent your application. To create one, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
<Note>
It's possible to use the API with **E2EE** enabled but this means learning about how encryption works with Infisical and performing client-side encryption/decryption operations yourself.
yourself.
If **E2EE** is a must for your team, we recommend either using one of the [Infisical SDKs](/documentation/getting-started/sdks) or checking out the [examples for E2EE](/api-reference/overview/examples/e2ee-disabled).
</Note>
![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)
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)
## Configuration
</Step>
<Step title="Create a Client Secret">
In order to use the identity, you'll need the non-sensitive **Client ID**
of the identity and a **Client Secret** for it; you can think of these credentials akin to a username
and password used to authenticate with the Infisical API. With that, press on the key icon on the identity to generate a **Client Secret**
for it.
![identities client secret create](../../images/platform/identities/identities-org-client-secret.png)
![identities client secret create](../../images/platform/identities/identities-org-client-secret-create-1.png)
![identities client secret create](../../images/platform/identities/identities-org-client-secret-create-2.png)
</Step>
<Step title="Add the identity to the project">
To enable the identity to access your project, we need to add it to the project. To do this, head over to the **Demo App** Project Settings > Access Control > Machine Identities and press **Add identity**.
Head to your Project Settings, where you created your service token, and un-check the **E2EE** setting.
Next, select the identity you want to add to the project and the role you want to assign it.
## Retrieve Secret
![identities project](../../images/platform/identities/identities-project.png)
![identities project create](../../images/platform/identities/identities-project-create.png)
</Step>
<Step title="Get an access token for the Infisical API">
To access the Infisical API as the identity, you should first perform a login operation
that is to exchange the **Client ID** and **Client Secret** of the identity for an access token
by making a request to the `/api/v1/auth/universal-auth/login` endpoint.
#### Sample request
Retrieve a secret from the project and environment in Infisical scoped to your service token by making a HTTP request with the following format/details:
```
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=<client_secret>' \
--data-urlencode 'clientId=<client_id>'
```
#### Sample response
```
{
"accessToken": "...",
"expiresIn": 7200,
"tokenType": "Bearer"
}
```
```bash
curl --location --request GET 'https://app.infisical.com/api/v3/secrets/raw/secretName?workspaceId=workspaceId&environment=environment' \
--header 'Authorization: Bearer serviceToken'
```
Next, we can use the access token to authenticate with the [Infisical API](/api-reference/overview/introduction) to read/write secrets
<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.
<ParamField path="secretName" type="string" required>
Name of secret to retrieve
</ParamField>
<ParamField query="workspaceId" type="string" required>
The ID of the workspace
</ParamField>
<ParamField query="environment" type="string" required>
The environment slug
</ParamField>
<ParamField query="secretPath" type="string" default="/" optional>
Path to secrets in workspace
</ParamField>
<ParamField query="type" type="string" optional default="personal">
The type of the secret. Valid options are “shared” or “personal”
</ParamField>
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 from the aforementioned login operation.
</Note>
</Step>
<Step title="Fetch back secret">
Finally, you can fetch the secret `FOO=BAR` back from **Step 1** by including the access token in the previous step in another request to the `/api/v3/secrets/raw/{secretName}` endpoint.
Depending on your application requirements, you may wish to use Infisical's API in different ways such as by retaining **E2EE**
or fetching multiple secrets at once instead of one at a time.
### Sample request
```
curl --location --request GET 'http://localhost:8080/api/v3/secrets/raw/FOO?workspaceId=657830d579cfc8415d06ce5b&environment=dev' \
--header 'Authorization: Bearer <access_token>'
```
### Sample response
Whatever the case, we recommend glossing over the [API Examples](/api-reference/overview/examples/note)
to gain a deeper understanding of how you to best leverage the Infisical API for your use-case.
```
{
"secret": {
"_id": "6564234b934d634e1fcd6cdf",
"version": 1,
"workspace": "6564173e934d634e1fcd6950",
"type": "shared",
"environment": "dev",
"secretKey": "FOO2",
"secretValue": "BAR2",
"secretComment": ""
}
}
```
Note that you can fetch a list of secrets back by making a request to the `/api/v3/secrets/raw` endpoint.
</Step>
</Steps>
See also:
- Explore the [API Examples](/api-reference/overview/examples/note)
- [API Reference](/api-reference/overview/introduction)

@ -13,7 +13,8 @@ Prerequisites:
Follow the instructions for your language use the SDK for it:
- [Node SDK](https://github.com/Infisical/infisical-node)
- [Python SDK](https://github.com/Infisical/infisical-python)
- [Node SDK](https://infisical.com/docs/sdks/languages/node)
- [Python SDK](https://infisical.com/docs/sdks/languages/python)
- [Java SDK](https://infisical.com/docs/sdks/languages/java)
Missing a language? [Throw in a request](https://github.com/Infisical/infisical/issues).

@ -240,12 +240,6 @@ At this stage, you know how to use the Infisical-Vercel integration to sync prod
Check out the [security guide](/security/overview).
</Accordion>
<Accordion title="Is there way to retain end-to-end encryption for syncing production secrets to Vercel?">
Yes. You can also use the Infisical [Node SDK](https://github.com/Infisical/infisical-node) to fetch secrets back to your Next.js app
in both development and production.
Depending on how you use it, however, it may require certain pages to be server-side rendered.
</Accordion>
</AccordionGroup>
See also:

@ -0,0 +1,53 @@
---
title: Identities
description: "Programmatically interact with Infisical"
---
<Note>
Currently, identities can only be used to make authenticated requests to the Infisical API and SDKs. They do not work with clients such as CLI, K8s Operator, Terraform Provider, etc.
We will be releasing compatibility with it across clients in the coming quarter.
</Note>
## Concept
A (machine) identity is an entity that you can create in an Infisical organization to represent a workload or application that requires access to the Infisical API. 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.
Key Features:
- Role Assignment: Identities must be assigned [roles](/documentation/platform/role-based-access-controls). These roles determine the scope of access to resources, either at the organization level or project level.
- Auth/Token Configuration: Identities must be configured with auth methods and access token properties to securely interact with the Infisical API.
## Workflow
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).
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.
Check out the following authentication method-specific guides for step-by-step instruction on how to use identities to access Infisical:
- [Universal Auth](/documentation/platform/identities/universal-auth)
**FAQ**
<AccordionGroup>
<Accordion title="What is the difference between an identity and service token?">
A service token is a project-level authentication method that is being phased out in favor of identities.
Amongst many differences, identities provide broader access over the Infisical API, utilizes the same role-based
permission system used by users, and comes with ample more configurable authentication and security features.
</Accordion>
<Accordion title="Why can I not create, read, update, or delete an identity?">
There are a few reasons for why this might happen:
- You have insufficient organization permissions to create, read, update, delete identities.
- The identity you are trying to read, update, or delete is more privileged than yourself.
- The role you are trying to create an identity for or update an identity to is more privileged than yours.
</Accordion>
</AccordionGroup>

@ -0,0 +1,140 @@
---
title: Universal Auth
description: "Authenticate with Infisical from any platform/environment"
---
**Universal Auth** is the most versatile authentication method that can be configured on an identity from any platform/environment to access Infisical.
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.
## Properties
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.
## Workflow
In the following steps, we explore how to create and use identities for your workloads and applications to access the Infisical API
using the Universal 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 **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.
- 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.
- Client Secret Trusted IPs: The IPs or CIDR ranges that the **Client Secret** can be used from together with the **Client ID** to get back an access token. By default, **Client Secrets** are given the `0.0.0.0/0`, allowing usage from any network address.
- 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.
<Warning>
Restricting **Client Secret** and access token usage to specific trusted IPs is a paid feature.
If youre using Infisical Cloud, then it is available under the Pro Tier. If youre self-hosting Infisical, then you should contact team@infisical.com to purchase an enterprise license to use it.
</Warning>
</Step>
<Step title="Creating a Client Secret">
In order to use the identity, you'll need the non-sensitive **Client ID**
of the identity and a **Client Secret** for it; you can think of these credentials akin to a username
and password used to authenticate with the Infisical API. With that, press on the key icon on the identity to generate a **Client Secret**
for it.
![identities client secret create](/images/platform/identities/identities-org-client-secret.png)
![identities client secret create](/images/platform/identities/identities-org-client-secret-create-1.png)
![identities client secret create](/images/platform/identities/identities-org-client-secret-create-2.png)
Feel free to input any (optional) details for the **Client Secret** configuration:
- Description: A description for the **Client Secret**.
- TTL (default is `0`): The time-to-live for the **Client Secret**. By default, the TTL will be set to 0 which implies that the **Client Secret** will never expire; a value of `0` implies an infinite lifetime.
- Max Number of Uses (default is `0`): The maximum number of times that the **Client Secret** can be used together with the **Client ID** to get back an access token; a value of `0` implies infinite number of uses.
</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 should first perform a login operation
that is to exchange the **Client ID** and **Client Secret** of the identity for an access token
by making a request to the `/api/v1/auth/universal-auth/login` endpoint.
#### Sample 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=...'
```
#### Sample response
```
{
"accessToken": "...",
"expiresIn": 7200,
"accessTokenMaxTTL": 43244
"tokenType": "Bearer"
}
```
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.
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>
**FAQ**
<AccordionGroup>
<Accordion title="Why is the Infisical API rejecting my identity credentials?">
There are a few reasons for why this might happen:
- The client secret or access token has expired.
- The identity is insufficently permissioned to interact with the resources you wish to access.
- You are attempting to access a `/raw` secrets endpoint that requires your project to disable E2EE.
- The client secret/access token is being used from an untrusted IP.
</Accordion>
<Accordion title="What is access token renewal and TTL/Max TTL?">
A identity access token can have a time-to-live (TTL) or incremental lifetime afterwhich it expires.
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
</Accordion>
</AccordionGroup>

@ -1,168 +0,0 @@
---
title: Identity
description: "Programmatically interact with Infisical"
---
A (machine) identity is an entity that you can create in Infisical.
Each identity represents a workload that wishes to access the Infisical API via an authentication method; this is similar to an IAM user in AWS or service account in GCP.
An identity can be provisioned scoped access to resources at the organization or project-level via [role-based access controls (RBAC)](/documentation/platform/role-based-access-controls). For instance, you may create a identity with scoped access to
fetch secrets back from the `/` path of the `development` environment in some project.
<Note>
The identity feature is in beta.
Currently, an identity can only be used to make authenticated requests to the Infisical API and does not work with any clients such as [Node SDK](https://github.com/Infisical/infisical-node)
, [Python SDK](https://github.com/Infisical/infisical-python), CLI, K8s operator, Terraform Provider, etc.
We will be releasing compatibility with it across clients in the coming quarter.
</Note>
Each identity can be configured an authentication method. The only supported method at the moment is **Universal Auth (UA)**
which has the following properties:
- In UA, each identity is assigned 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 (i.e. login operation) to authenticate with the Infisical API.
- UA supports restrictions on the number of times that the **Client Secret(s)** and access token(s) can be used.
- UA supports token renewal that is the ability to extend the lifetime of a token by its TTL up to its maximum TTL since its creation.
- UA supports IP allowlisting; this means you can restrict the usage of **Client Secret(s)** and access token to a specific IP or CIDR range.
- UA support expiration, so, if specified, the client secret of the identity will automatically be defunct after a period of time.
- UA tracks most recent usage of their client secrets and access tokens; it also keeps track of each token's usage count.
## Using identities
In the following steps, we explore how to create and use identities for your applications to access the Infisical API.
<Steps>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
![machine identities organization](../../images/platform/machine-identity/machine-identity-org.png)
![machine identities organization create](../../images/platform/machine-identity/machine-identity-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 to permit the identity to access certain resources.
Once you've created an identity, you'll be prompted to configure the **Universal Auth** authentication method for it.
- Access Token TTL (default is `7200`): The incremental lifetime for an acccess token in seconds; a value of `0` implies an infinite incremental lifetime.
- Access Token Max TTL (default is `7200`): The maximum lifetime for an acccess token in seconds; a value of `0` implies an infinite maximum lifetime.
- 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.
- Client Secret Trusted IPs: The IPs or CIDR ranges that the **Client Secret** can be used from together with the **Client ID** to get back an access token. By default, **Client Secrets** are given the `0.0.0.0/0` entry representing all possible IPv4 addresses.
- 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` entry representing all possible IPv4 addresses.
<Warning>
Restricting **Client Secret** and access token usage to specific trusted IPs is a paid feature.
If youre using Infisical Cloud, then it is available under the Pro Tier. If youre self-hosting Infisical, then you should contact team@infisical.com to purchase an enterprise license to use it.
</Warning>
</Step>
<Step title="Creating a Client Secret">
In order to use the identity, you'll need the non-sensitive **Client ID**
of the identity and a **Client Secret** for it; you can think of these credentials akin to a username
and password used to authenticate with the Infisical API. With that, press on the key icon on the identity to generate a **Client Secret**
for it.
![machine identities client secret create](../../images/platform/machine-identity/machine-identity-org-client-secret.png)
![machine identities client secret create](../../images/platform/machine-identity/machine-identity-org-client-secret-create-1.png)
![machine identities client secret create](../../images/platform/machine-identity/machine-identity-org-client-secret-create-2.png)
Feel free to input any (optional) details for the **Client Secret** configuration:
- Description: A description for the **Client Secret**.
- TTL (default is `0`): The time-to-live for the **Client Secret**. By default, the TTL will be set to 0 which implies that the **Client Secret** will never expire; a value of `0` implies an infinite lifetime.
- Max Number of Uses (default is `0`): The maximum number of times that the **Client Secret** can be used together with the **Client ID** to get back an access token; a value of `0` implies infinite number of uses.
</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 role you want to assign it.
![machine identities project](../../images/platform/machine-identity/machine-identity-project.png)
![machine identities project create](../../images/platform/machine-identity/machine-identity-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
that is to exchange the **Client ID** and **Client Secret** of the MI for an access token
by making a request to the `/api/v1/auth/universal-auth/login` endpoint.
#### Sample 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=...'
```
#### Sample response
```
{
"accessToken": "...",
"expiresIn": 7200,
"tokenType": "Bearer"
}
```
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.
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 from the aforementioned login operation.
</Note>
</Step>
</Steps>
**FAQ**
<AccordionGroup>
<Accordion title="What is the difference between an identity and service token?">
A service token is a project-level authentication method that is being phased out in favor of identities.
Amongst many differences, identities provide broader access over the Infisical API, utilizes the same role-based
permission system used by users, and comes with ample more configurable security measures.
</Accordion>
<Accordion title="Why is the Infisical API rejecting my identity credentials?">
There are a few reasons for why this might happen:
- The client secret or access token has expired.
- The identity is insufficently permissioned to interact with the resources you wish to access.
- You are attempting to access a `/raw` secrets endpoint that requires your project to disable E2EE.
- The client secret/access token is being used from an untrusted IP.
</Accordion>
<Accordion title="What is token renewal and TTL/Max TTL?">
A identity access token can have a time-to-live (TTL) or incremental lifetime afterwhich it expires.
In certain cases, you may want to extend the lifespan of an access token; to do so, you must use the max TTL parameter.
When TTL and max TTL are equal, a token is not renewable; when max TTL is greater than TTL, a token is renewable.
In the latter case, a token still expires at its TTL but its lifetime can be extended/renewed up until its max TLL.
Note that the max TTL cannot be less than the TTL for an access token.
</Accordion>
<Accordion title="Why can I not create, read, update, or delete an identity?">
There are a few reasons for why this might happen:
- You have insufficient organization permissions to create, read, update, delete identities.
- The identity you are trying to read, update, or delete is more privileged than yourself.
- The role you are trying to create an identity for or update an identity to is more privileged than yours.
</Accordion>
<Accordion title="Can you provide examples for using glob patterns?">
1. `/**`: This pattern matches all folders at any depth in the directory structure. For example, it would match folders like `/folder1/`, `/folder1/subfolder/`, and so on.
2. `/*`: This pattern matches all immediate subfolders in the current directory. It does not match any folders at a deeper level. For example, it would match folders like `/folder1/`, `/folder2/`, but not `/folder1/subfolder/`.
3. `/*/*`: This pattern matches all subfolders at a depth of two levels in the current directory. It does not match any folders at a shallower or deeper level. For example, it would match folders like `/folder1/subfolder/`, `/folder2/subfolder/`, but not `/folder1/` or `/folder1/subfolder/subsubfolder/`.
4. `/folder1/*`: This pattern matches all immediate subfolders within the `/folder1/` directory. It does not match any folders outside of `/folder1/`, nor does it match any subfolders within those immediate subfolders. For example, it would match folders like `/folder1/subfolder1/`, `/folder1/subfolder2/`, but not `/folder2/subfolder/`.
</Accordion>
</AccordionGroup>

@ -12,8 +12,8 @@ This means that updating the value of a base secret propagates directly to other
Currently, the secret referencing feature is only supported by the
[Infisical CLI](/cli/overview) and [native integrations](/integrations/overview).
We intend to add support for it to the [Node SDK](https://github.com/Infisical/infisical-node)
and [Python SDK](https://github.com/Infisical/infisical-python) this quarter.
We intend to add support for it to the [Node SDK](https://infisical.com/docs/sdks/languages/node),
[Python SDK](https://infisical.com/docs/sdks/languages/python), and [Java SDK](https://infisical.com/docs/sdks/languages/java) this quarter.
</Note>
![secret referencing](../../images/platform/secret-references-imports/secret-reference.png)

@ -10,97 +10,97 @@ description: "Configure Azure SAML for Infisical SSO"
then you should contact team@infisical.com to purchase an enterprise license to use it.
</Info>
1. In Infisical, head over to your organization Settings > Authentication > SAML SSO Configuration and select **Set up SAML SSO**.
Next, copy the **Reply URL (Assertion Consumer Service URL)** and **Identifier (Entity ID)** to use when configuring the Azure SAML application.
<Steps>
<Step title="Prepare the SAML SSO configuration in Infisical">
In Infisical, head over to your organization Settings > Authentication > SAML SSO Configuration and select **Set up SAML SSO**.
![Azure SAML initial configuration](../../../images/sso/azure/init-config.png)
Next, copy the **Reply URL (Assertion Consumer Service URL)** and **Identifier (Entity ID)** to use when configuring the Azure SAML application.
2. In the Azure Portal, navigate to the Azure Active Directory and select **Enterprise applications**. On this screen, select
**+ New application**.
![Azure SAML initial configuration](../../../images/sso/azure/init-config.png)
</Step>
<Step title="Create a SAML application in Azure">
In the Azure Portal, navigate to the Azure Active Directory and select **Enterprise applications**. On this screen, select **+ New application**.
![Azure SAML enterprise applications](../../../images/sso/azure/enterprise-applications.png)
![Azure SAML enterprise applications](../../../images/sso/azure/enterprise-applications.png)
![Azure SAML new application](../../../images/sso/azure/new-application.png)
![Azure SAML new application](../../../images/sso/azure/new-application.png)
On the next screen, press the **+ Create your own application** button.
Give the application a unique name like Infisical; choose the "Integrate any other application you don't find in the gallery (Non-gallery)"
option and hit the **Create** button.
2. On the next screen, press the **+ Create your own application** button.
Give the application a unique name like Infisical; choose the "Integrate any other application you don't find in the gallery (Non-gallery)"
option and hit the **Create** button.
![Azure SAML create own application](../../../images/sso/azure/create-own-application.png)
![Azure SAML create own application](../../../images/sso/azure/create-own-application.png)
On the application overview screen, select **Single sign-on** from the left sidebar. From there, select the **SAML** single sign-on method.
3. On the application overview screen, select **Single sign-on** from the left sidebar. From there,
select the **SAML** single sign-on method.
![Azure SAML sign on method](../../../images/sso/azure/sso-method.png)
![Azure SAML sign on method](../../../images/sso/azure/sso-method.png)
Next, select **Edit** in the **Basic SAML Configuration** section and add/set the **Identifier (Entity ID)** to **Entity ID** and add/set the **Reply URL (Assertion Consumer Service URL)** to **ACS URL** from step 1.
4. Next, select **Edit** in the **Basic SAML Configuration** section and add/set the **Identifier (Entity ID)**
to **Entity ID** and add/set the **Reply URL (Assertion Consumer Service URL)** to **ACS URL** from step 1.
![Azure SAML edit basic configuration](../../../images/sso/azure/edit-basic-config.png)
![Azure SAML edit basic configuration](../../../images/sso/azure/edit-basic-config.png)
![Azure SAML edit basic configuration 2](../../../images/sso/azure/edit-basic-config-2.png)
![Azure SAML edit basic configuration 2](../../../images/sso/azure/edit-basic-config-2.png)
<Note>
If you're self-hosting Infisical, then you will want to replace
`https://app.infisical.com` with your own domain.
</Note>
<Note>
If you're self-hosting Infisical, then you will want to replace
`https://app.infisical.com` with your own domain.
</Note>
Back in the **Set up Single Sign-On with SAML** screen, select **Edit** in the **Attributes & Claims** section and configure the following map:
5. Back in the **Set up Single Sign-On with SAML** screen, select **Edit** in the **Attributes & Claims** section and configure the following map:
- `email -> user.userprinciplename`
- `firstName -> user.firstName`
- `lastName -> user.lastName`
- `email -> user.userprinciplename`
- `firstName -> user.firstName`
- `lastName -> user.lastName`
![Azure SAML edit attributes and claims](../../../images/sso/azure/edit-attributes-claims.png)
![Azure SAML edit attributes and claims](../../../images/sso/azure/edit-attributes-claims.png)
![Azure SAML edit attributes and claims 2](../../../images/sso/azure/edit-attributes-claims-2.png)
![Azure SAML edit attributes and claims 2](../../../images/sso/azure/edit-attributes-claims-2.png)
Back in the **Set up Single Sign-On with SAML** screen, select **Edit** in the **SAML Certificates** section and set the **Signing Option** field to **Sign SAML response and assertion**.
6. Back in the **Set up Single Sign-On with SAML** screen, select **Edit** in the **SAML Certificates** section and set the **Signing Option** field to **Sign SAML response and assertion**.
![Azure SAML edit certificate](../../../images/sso/azure/edit-saml-certificate.png)
![Azure SAML edit certificate](../../../images/sso/azure/edit-saml-certificate.png)
![Azure SAML edit certificate signing option](../../../images/sso/azure/edit-saml-certificate-2.png)
</Step>
<Step title="Retrieve Identity Provider (IdP) Information from Okta">
In the **Set up Single Sign-On with SAML** screen, copy the **Login URL** and **SAML Certificate** to use when finishing configuring Azure SAML in Infisical.
![Azure SAML edit certificate signing option](../../../images/sso/azure/edit-saml-certificate-2.png)
![Azure SAML identity provider values 1](../../../images/sso/azure/idp-values.png)
7. Get IdP values:
In the **Properties** screen, copy the **Application ID** to use when finishing configuring Azure SAML in Infisical.
In the **Set up Single Sign-On with SAML** screen, copy the **Login URL** and **SAML Certificate** to use when finishing configuring Azure SAML in Infisical.
![Azure SAML identity provider values 2](../../../images/sso/azure/idp-values-2.png)
</Step>
<Step title="Finish configuring SAML in Infisical">
Back in Infisical, set **Login URL**, **Azure Application ID**, and **SAML Certificate** from step 3. Once you've done that, press **Update** to complete the required configuration.
![Azure SAML identity provider values 1](../../../images/sso/azure/idp-values.png)
![Azure SAML paste identity provider values](../../../images/sso/azure/idp-values-3.png)
In the **Properties** screen, copy the **Application ID** to use when finishing configuring Azure SAML in Infisical.
<Note>
When pasting the certificate into Infisical, you'll want to retain `-----BEGIN
CERTIFICATE-----` and `-----END CERTIFICATE-----` at the first and last line
of the text area respectively.
![Azure SAML identity provider values 2](../../../images/sso/azure/idp-values-2.png)
Having trouble?, try copying the X509 certificate information from the Federation Metadata XML file in Azure.
Back in Infisical, set **Login URL**, **Azure Application ID**, and **SAML Certificate** from above. Once you've done that, press **Update** to complete the required configuration.
</Note>
</Step>
<Step title="Assign users in Azure to the application">
Back in Azure, navigate to the **Users and groups** tab and select **+ Add user/group** to assign access to the login with SSO application on a user or group-level.
![Azure SAML assignment](../../../images/sso/azure/assignment.png)
</Step>
<Step title="Enable SAML SSO in Infisical">
Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Azure.
![Azure SAML paste identity provider values](../../../images/sso/azure/idp-values-3.png)
<Note>
When pasting the certificate into Infisical, you'll want to retain `-----BEGIN
CERTIFICATE-----` and `-----END CERTIFICATE-----` at the first and last line
of the text area respectively.
Having trouble?, try copying the X509 certificate information from the Federation Metadata XML file in Azure.
</Note>
7. Assignments
Back in Azure, navigate to the **Users and groups** tab and select **+ Add user/group** to assign access to the login with SSO application on a user or group-level.
![Azure SAML assignment](../../../images/sso/azure/assignment.png)
8. Return to Infisical and enable SAML SSO.
Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Azure.
![Azure SAML assignment](../../../images/sso/azure/enable-saml.png)
![Azure SAML assignment](../../../images/sso/azure/enable-saml.png)
</Step>
</Steps>
<Note>
If you're configuring SAML SSO on a self-hosted instance of Infisical, make sure to
set the `JWT_PROVIDER_AUTH_SECRET` and `SITE_URL` environment variable for it to work:
set the `AUTH_SECRET` and `SITE_URL` environment variable for it to work:
- `JWT_PROVIDER_AUTH_SECRET`: This is secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
- `AUTH_SECRET`: A secret key used for signing and verifying JWT. This can be a random 32-byte base64 string generated with `openssl rand -base64 32`.
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
</Note>
</Note>

@ -5,38 +5,39 @@ description: "Configure GitHub SSO for Infisical"
Using GitHub SSO on a self-hosted instance of Infisical requires configuring an OAuth2 application in GitHub and registering your instance with it.
## Create an OAuth application in GitHub
<Steps>
<Step title="Create an OAuth application in GitHub">
Navigate to your user Settings > Developer settings > OAuth Apps to create a new GitHub OAuth application.
Navigate to your user Settings > Developer settings > OAuth Apps to create a new GitHub OAuth application.
![GitHub settings](../../../images/sso/github/settings.png)
![GitHub developer settings](../../../images/sso/github/dev-settings.png)
![GitHub create new OAuth application](../../../images/sso/github/new-app.png)
![GitHub settings](../../../images/sso/github/settings.png)
![GitHub developer settings](../../../images/sso/github/dev-settings.png)
![GitHub create new OAuth application](../../../images/sso/github/new-app.png)
Create the OAuth application. As part of the form, set the **Homepage URL** to your self-hosted domain `https://your-domain.com`
and the **Authorization callback URL** to `https://your-domain.com/api/v1/sso/github`.
Create the OAuth application. As part of the form, set the **Homepage URL** to your self-hosted domain `https://your-domain.com`
and the **Authorization callback URL** to `https://your-domain.com/api/v1/sso/github`.
![GitHub create new OAuth application form](../../../images/sso/github/new-app-form.png)
![GitHub create new OAuth application form](../../../images/sso/github/new-app-form.png)
<Note>
If you have a GitHub organization, you can create an OAuth application under it
in your organization Settings > Developer settings > OAuth Apps > New Org OAuth App.
</Note>
</Step>
<Step title="Add your OAuth application credentials to Infisical">
Obtain the **Client ID** and generate a new **Client Secret** for your GitHub OAuth application.
<Note>
If you have a GitHub organization, you can create an OAuth application under it
in your organization Settings > Developer settings > OAuth Apps > New Org OAuth App.
</Note>
![GCP obtain OAuth2 credentials](../../../images/sso/github/credentials.png)
## Add your OAuth application credentials to Infisical
Back in your Infisical instance, make sure to set the following environment variables:
Obtain the **Client ID** and generate a new **Client Secret** for your GitHub OAuth application.
![GCP obtain OAuth2 credentials](../../../images/sso/github/credentials.png)
Back in your Infisical instance, make sure to set the following environment variables:
- `CLIENT_ID_GITHUB_LOGIN`: The **Client ID** of your GitHub OAuth application.
- `CLIENT_SECRET_GITHUB_LOGIN`: The **Client Secret** of your GitHub OAuth application.
- `JWT_PROVIDER_AUTH_SECRET`: A secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
Once added, restart your Infisical instance and log in with GitHub.
- `CLIENT_ID_GITHUB_LOGIN`: The **Client ID** of your GitHub OAuth application.
- `CLIENT_SECRET_GITHUB_LOGIN`: The **Client Secret** of your GitHub OAuth application.
- `AUTH_SECRET`: A secret key used for signing and verifying JWT. This can be a random 32-byte base64 string generated with `openssl rand -base64 32`.
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
Once added, restart your Infisical instance and log in with GitHub.
</Step>
</Steps>
## FAQ
@ -45,7 +46,7 @@ Once added, restart your Infisical instance and log in with GitHub.
It is likely that you have misconfigured your self-hosted instance of Infisical. You should:
- Check that you have set the `CLIENT_ID_GITHUB_LOGIN`, `CLIENT_SECRET_GITHUB_LOGIN`,
`JWT_PROVIDER_AUTH_SECRET`, and `SITE_URL` environment variables.
`AUTH_SECRET`, and `SITE_URL` environment variables.
- Check that the **Authorization callback URL** specified in GitHub matches the `SITE_URL` environment variable.
For example, if the former is `https://app.infisical.com/api/v1/sso/github` then the latter should be `https://app.infisical.com`.
</Accordion>

@ -5,38 +5,39 @@ description: "Configure GitLab SSO for Infisical"
Using GitLab SSO on a self-hosted instance of Infisical requires configuring an OAuth application in GitLab and registering your instance with it.
## Create an OAuth application in GitLab
<Steps>
<Step title="Create an OAuth application in GitLab">
Navigate to your user Settings > Applications to create a new GitLab application.
Navigate to your user Settings > Applications to create a new GitLab application.
![sso gitlab config](/images/sso/gitlab/edit-profile.png)
![sso gitlab config](/images/sso/gitlab/new-app.png)
![sso gitlab config](/images/sso/gitlab/edit-profile.png)
![sso gitlab config](/images/sso/gitlab/new-app.png)
Create the application. As part of the form, set the **Redirect URI** to `https://your-domain.com/api/v1/sso/gitlab`.
Note that only `read_user` is required as part of the **Scopes** configuration.
Create the application. As part of the form, set the **Redirect URI** to `https://your-domain.com/api/v1/sso/gitlab`.
Note that only `read_user` is required as part of the **Scopes** configuration.
![sso gitlab config](/images/sso/gitlab/new-app-form.png)
![sso gitlab config](/images/sso/gitlab/new-app-form.png)
<Note>
If you have a GitLab group, you can create an OAuth application under it
in your group Settings > Applications.
</Note>
</Step>
<Step title="Add your OAuth application credentials to Infisical">
Obtain the **Application ID** and **Secret** for your GitLab application.
<Note>
If you have a GitLab group, you can create an OAuth application under it
in your group Settings > Applications.
</Note>
![sso gitlab config](/images/sso/gitlab/credentials.png)
## Add your OAuth application credentials to Infisical
Back in your Infisical instance, make sure to set the following environment variables:
Obtain the **Application ID** and **Secret** for your GitLab application.
![sso gitlab config](/images/sso/gitlab/credentials.png)
Back in your Infisical instance, make sure to set the following environment variables:
- `CLIENT_ID_GITLAB_LOGIN`: The **Client ID** of your GitLab application.
- `CLIENT_SECRET_GITLAB_LOGIN`: The **Secret** of your GitLab application.
- (optional) `URL_GITLAB_LOGIN`: The URL of your self-hosted instance of GitLab where the OAuth application is registered. If no URL is passed in, this will default to `https://gitlab.com`.
- `JWT_PROVIDER_AUTH_SECRET`: A secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
Once added, restart your Infisical instance and log in with GitLab.
- `CLIENT_ID_GITLAB_LOGIN`: The **Client ID** of your GitLab application.
- `CLIENT_SECRET_GITLAB_LOGIN`: The **Secret** of your GitLab application.
- (optional) `URL_GITLAB_LOGIN`: The URL of your self-hosted instance of GitLab where the OAuth application is registered. If no URL is passed in, this will default to `https://gitlab.com`.
- `AUTH_SECRET`: A secret key used for signing and verifying JWT. This can be a random 32-byte base64 string generated with `openssl rand -base64 32`.
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
Once added, restart your Infisical instance and log in with GitLab.
</Step>
</Steps>
## FAQ
@ -45,7 +46,7 @@ Once added, restart your Infisical instance and log in with GitLab.
It is likely that you have misconfigured your self-hosted instance of Infisical. You should:
- Check that you have set the `CLIENT_ID_GITLAB_LOGIN`, `CLIENT_SECRET_GITLAB_LOGIN`,
`JWT_PROVIDER_AUTH_SECRET`, and `SITE_URL` environment variables.
`AUTH_SECRET`, and `SITE_URL` environment variables.
- Check that the **Redirect URI** specified in GitLab matches the `SITE_URL` environment variable.
For example, if the former is `https://app.infisical.com/api/v1/sso/gitlab` then the latter should be `https://app.infisical.com`.
</Accordion>

@ -5,31 +5,32 @@ description: "Configure Google SSO for Infisical"
Using Google SSO on a self-hosted instance of Infisical requires configuring an OAuth2 application in GCP and registering your instance with it.
## Create an OAuth2 application in GCP
<Steps>
<Step title="Create an OAuth2 application in GCP">
Navigate to your project API & Services > Credentials to create a new OAuth2 application.
![GCP API services](../../../images/sso/google/api-services.png)
![GCP create new OAuth2 application](../../../images/sso/google/new-app.png)
Navigate to your project API & Services > Credentials to create a new OAuth2 application.
![GCP API services](../../../images/sso/google/api-services.png)
![GCP create new OAuth2 application](../../../images/sso/google/new-app.png)
Create the application. As part of the form, add to **Authorized redirect URIs**: `https://your-domain.com/api/v1/sso/google`.
Create the application. As part of the form, add to **Authorized redirect URIs**: `https://your-domain.com/api/v1/sso/google`.
![GCP create new OAuth2 application form](../../../images/sso/google/new-app-form.png)
</Step>
<Step title="Add your OAuth2 application credentials to Infisical">
Obtain the **Client ID** and **Client Secret** for your GCP OAuth2 application.
![GCP create new OAuth2 application form](../../../images/sso/google/new-app-form.png)
![GCP obtain OAuth2 credentials](../../../images/sso/google/credentials.png)
Back in your Infisical instance, make sure to set the following environment variables:
## Add your OAuth2 application credentials to Infisical
Obtain the **Client ID** and **Client Secret** for your GCP OAuth2 application.
![GCP obtain OAuth2 credentials](../../../images/sso/google/credentials.png)
Back in your Infisical instance, make sure to set the following environment variables:
- `CLIENT_ID_GOOGLE_LOGIN`: The **Client ID** of your GCP OAuth2 application.
- `CLIENT_SECRET_GOOGLE_LOGIN`: The **Client Secret** of your GCP OAuth2 application.
- `JWT_PROVIDER_AUTH_SECRET`: A secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
Once added, restart your Infisical instance and log in with Google
- `CLIENT_ID_GOOGLE_LOGIN`: The **Client ID** of your GCP OAuth2 application.
- `CLIENT_SECRET_GOOGLE_LOGIN`: The **Client Secret** of your GCP OAuth2 application.
- `AUTH_SECRET`: A secret key used for signing and verifying JWT. This can be a random 32-byte base64 string generated with `openssl rand -base64 32`.
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
Once added, restart your Infisical instance and log in with Google
</Step>
</Steps>
## FAQ
@ -38,7 +39,7 @@ Once added, restart your Infisical instance and log in with Google
It is likely that you have misconfigured your self-hosted instance of Infisical. You should:
- Check that you have set the `CLIENT_ID_GOOGLE_LOGIN`, `CLIENT_SECRET_GOOGLE_LOGIN`,
`JWT_PROVIDER_AUTH_SECRET`, and `SITE_URL` environment variables.
`AUTH_SECRET`, and `SITE_URL` environment variables.
- Check that the **Authorized redirect URI** specified in GCP matches the `SITE_URL` environment variable.
For example, if the former is `https://app.infisical.com/api/v1/sso/google` then the latter should be `https://app.infisical.com`.
</Accordion>

@ -10,73 +10,77 @@ description: "Configure JumpCloud SAML for Infisical SSO"
then you should contact team@infisical.com to purchase an enterprise license to use it.
</Info>
1. In Infisical, head over to your organization Settings > Authentication > SAML SSO Configuration and select **Set up SAML SSO**.
Next, copy the **ACS URL** and **SP Entity ID** to use when configuring the JumpCloud SAML application.
<Steps>
<Step title="Prepare the SAML SSO configuration in Infisical">
In Infisical, head over to your organization Settings > Authentication > SAML SSO Configuration and select **Set up SAML SSO**.
![JumpCloud SAML initial configuration](../../../images/sso/jumpcloud/init-config.png)
Next, copy the **ACS URL** and **SP Entity ID** to use when configuring the JumpCloud SAML application.
2. In the JumpCloud Admin Portal, navigate to User Authentication > SSO and create an application. If this is your first application, select **Get Started**;
if not, select **+Add New Application**
![JumpCloud SAML initial configuration](../../../images/sso/jumpcloud/init-config.png)
</Step>
<Step title="Create a SAML application in JumpCloud">
2.1. In the JumpCloud Admin Portal, navigate to User Authentication > SSO and create an application. If this is your first application, select **Get Started**; if not, select **+Add New Application**
![JumpCloud SAML new application](../../../images/sso/jumpcloud/new-application.png)
![JumpCloud SAML new application](../../../images/sso/jumpcloud/new-application.png)
3. Next, select **Custom SAML App** to open up the **New SSO** dialog.
2.2. Next, select **Custom SAML App** to open up the **New SSO** dialog.
![JumpCloud custom SAML app](../../../images/sso/jumpcloud/custom-saml-app.png)
![JumpCloud custom SAML app](../../../images/sso/jumpcloud/custom-saml-app.png)
4. In the **General Info** tab, give the application a unique name like Infisical.
2.3. In the **General Info** tab, give the application a unique name like Infisical.
![JumpCloud general info](../../../images/sso/jumpcloud/general-info.png)
![JumpCloud general info](../../../images/sso/jumpcloud/general-info.png)
5. In the **SSO** tab, set the **SP Entity ID** and **ACS URL** from step 1; set the **IdP Entity ID** to the same value as the **SP Entity ID**.
2.4. In the **SSO** tab, set the **SP Entity ID** and **ACS URL** from step 1; set the **IdP Entity ID** to the same value as the **SP Entity ID**.
![JumpCloud edit basic config](../../../images/sso/jumpcloud/edit-basic-config.png)
![JumpCloud edit basic config](../../../images/sso/jumpcloud/edit-basic-config.png)
6. On the same tab, check the **Sign Assertion** checkbox and fill the **IDP URL** to something unique.
Copy the **IDP URL** to use when finishing configuring the JumpCloud SAML in Infisical.
2.5. On the same tab, check the **Sign Assertion** checkbox and fill the **IDP URL** to something unique.
Copy the **IDP URL** to use when finishing configuring the JumpCloud SAML in Infisical.
![JumpCloud edit basic config 2](../../../images/sso/jumpcloud/edit-basic-config-2.png)
![JumpCloud edit basic config 2](../../../images/sso/jumpcloud/edit-basic-config-2.png)
7. On the same tab, in the **Attributes** section, configure the following map:
2.6. On the same tab, in the **Attributes** section, configure the following map:
- `email -> email`
- `firstName -> firstname`
- `lastName -> lastname`
- `email -> email`
- `firstName -> firstname`
- `lastName -> lastname`
![JumpCloud attribute statements](../../../images/sso/jumpcloud/attribute-statements.png)
![JumpCloud attribute statements](../../../images/sso/jumpcloud/attribute-statements.png)
Finally press activate to create the SAML application.
Finally press activate to create the SAML application.
8. Next, select the newly created SAML application and select **Download certificate** under the **IDP Certificate Valid** dropdown
2.7. Next, select the newly created SAML application and select **Download certificate** under the **IDP Certificate Valid** dropdown
![JumpCloud download certificate](../../../images/sso/jumpcloud/download-saml-certificate.png)
![JumpCloud download certificate](../../../images/sso/jumpcloud/download-saml-certificate.png)
</Step>
<Step title="Finish configuring SAML in Infisical">
Back in Infisical, set the **IDP URL** from step 2.5 and the **IdP Entity ID** from step 2.4. Also, paste the certificate from the previous step.
9. Back in Infisical, set the **IDP URL** from step 6 and the **IdP Entity ID** from step 5. Also, paste the certificate from the previous step.
![JumpCloud IdP values](../../../images/sso/jumpcloud/idp-values.png)
![JumpCloud IdP values](../../../images/sso/jumpcloud/idp-values.png)
<Note>
When pasting the certificate into Infisical, you'll want to retain `-----BEGIN
CERTIFICATE-----` and `-----END CERTIFICATE-----` at the first and last line
of the text area respectively.
</Note>
</Step>
<Step title="Assign users in JumpCloud to the application">
Back in JumpCloud, navigate to the **User Groups** tab and assign users to the newly created application.
<Note>
When pasting the certificate into Infisical, you'll want to retain `-----BEGIN
CERTIFICATE-----` and `-----END CERTIFICATE-----` at the first and last line
of the text area respectively.
</Note>
![JumpCloud SAML assignment](../../../images/sso/jumpcloud/assignment.png)
</Step>
<Step title="Enable SAML SSO in Infisical">
Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via JumpCloud.
10. Assignments
Back in JumpCloud, navigate to the **User Groups** tab and assign users to the newly created application.
![JumpCloud SAML assignment](../../../images/sso/jumpcloud/assignment.png)
11. Return to Infisical and enable SAML SSO.
Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via JumpCloud.
![JumpCloud SAML assignment](../../../images/sso/jumpcloud/enable-saml.png)
![JumpCloud SAML assignment](../../../images/sso/jumpcloud/enable-saml.png)
</Step>
</Steps>
<Note>
If you're configuring SAML SSO on a self-hosted instance of Infisical, make sure to
set the `JWT_PROVIDER_AUTH_SECRET` and `SITE_URL` environment variable for it to work:
set the `AUTH_SECRET` and `SITE_URL` environment variable for it to work:
- `JWT_PROVIDER_AUTH_SECRET`: This is secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
- `AUTH_SECRET`: A secret key used for signing and verifying JWT. This can be a random 32-byte base64 string generated with `openssl rand -base64 32`.
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
</Note>

@ -10,78 +10,80 @@ description: "Configure Okta SAML 2.0 for Infisical SSO"
then you should contact team@infisical.com to purchase an enterprise license to use it.
</Info>
1. In Infisical, head over to your organization Settings > Authentication > SAML SSO Configuration and select **Set up SAML SSO**.
Next, copy the **Single sign-on URL** and **Audience URI (SP Entity ID)** to use when configuring the Okta SAML 2.0 application.
<Steps>
<Step title="Prepare the SAML SSO configuration in Infisical">
In Infisical, head over to your organization Settings > Authentication > SAML SSO Configuration and select **Set up SAML SSO**.
Next, copy the **Single sign-on URL** and **Audience URI (SP Entity ID)** to use when configuring the Okta SAML 2.0 application.
![Okta SAML initial configuration](../../../images/sso/okta/init-config.png)
</Step>
<Step title="Create a SAML application in Okta">
In the Okta Admin Portal, select Applications > Applications from the navigation. On the Applications screen, select the **Create App Integration**
button.
![Okta SAML initial configuration](../../../images/sso/okta/init-config.png)
![SAML Okta create app integration](../../../images/sso/okta/create-app-integration.png)
In the Create a New Application Integration dialog, select the **SAML 2.0** radio button:
2. In the Okta Admin Portal, select Applications > Applications from the
navigation. On the Applications screen, select the **Create App Integration**
button.
![SAML Okta create SAML 2.0 integration](../../../images/sso/okta/create-saml-app.png)
On the General Settings screen, give the application a unique name like Infisical and select **Next**.
![SAML Okta create SAML 2.0 integration](../../../images/sso/okta/general-settings.png)
On the Configure SAML screen, set the **Single sign-on URL** and **Audience URI (SP Entity ID)** from step 1.
![SAML Okta create app integration](../../../images/sso/okta/create-app-integration.png)
![SAML Okta configure IdP fields](../../../images/sso/okta/configure-saml.png)
<Note>
If you're self-hosting Infisical, then you will want to replace
`https://app.infisical.com` with your own domain.
</Note>
Also on the Configure SAML screen, configure the **Attribute Statements** to map:
3. In the Create a New Application Integration dialog, select the **SAML 2.0** radio button:
- `id -> user.id`,
- `email -> user.email`,
- `firstName -> user.firstName`
- `lastName -> user.lastName`
![SAML Okta create SAML 2.0 integration](../../../images/sso/okta/create-saml-app.png)
![SAML Okta attribute statements](../../../images/sso/okta/attribute-statements.png)
4. On the General Settings screen, give the application a unique name like Infisical and select **Next**.
Once configured, select **Next** to proceed to the Feedback screen and select **Finish**.
</Step>
<Step title="Retrieve Identity Provider (IdP) Information from Okta">
Once your application is created, select the **Sign On** tab for the app and select the **View Setup Instructions** button located on the right side of the screen:
![SAML Okta create SAML 2.0 integration](../../../images/sso/okta/general-settings.png)
![SAML Okta view setup instructions](../../../images/sso/okta/view-setup-instructions.png)
5. On the Configure SAML screen, set the **Single sign-on URL** and **Audience URI (SP Entity ID)** from step 1.
Copy the **Identity Provider Single Sign-On URL**, the **Identity Provider Issuer**, and the **X.509 Certificate** to use when finishing configuring Okta SAML in Infisical.
![SAML Okta configure IdP fields](../../../images/sso/okta/configure-saml.png)
![SAML Okta IdP values](../../../images/sso/okta/idp-values.png)
</Step>
<Step title="Finish configuring SAML in Infisical">
Back in Infisical, set **Identity Provider Single Sign-On URL**, **Identity Provider Issuer**,
and **Certificate** to **X.509 Certificate** from step 3. Once you've done that, press **Update** to complete the required configuration.
<Note>
If you're self-hosting Infisical, then you will want to replace
`https://app.infisical.com` with your own domain.
</Note>
![SAML Okta paste values into Infisical](../../../images/sso/okta/idp-values-2.png)
</Step>
<Step title="Assign users in Okta to the application">
Back in Okta, navigate to the **Assignments** tab and select **Assign**. You can assign access to the application on a user-by-user basis using the Assign to People option, or in-bulk using the Assign to Groups option.
6. Also on the Configure SAML screen, configure the **Attribute Statements** to map:
![SAML Okta assignment](../../../images/sso/okta/assignment.png)
- `id -> user.id`,
- `email -> user.email`,
- `firstName -> user.firstName`
- `lastName -> user.lastName`
At this point, you have configured everything you need within the context of the Okta Admin Portal.
</Step>
<Step title="Enable SAML SSO in Infisical">
Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Okta.
![SAML Okta attribute statements](../../../images/sso/okta/attribute-statements.png)
Once configured, select **Next** to proceed to the Feedback screen and select **Finish**.
7. Get IdP values
Once your application is created, select the **Sign On** tab for the app and select the **View Setup Instructions** button located on the right side of the screen:
![SAML Okta view setup instructions](../../../images/sso/okta/view-setup-instructions.png)
Copy the **Identity Provider Single Sign-On URL**, the **Identity Provider Issuer**, and the **X.509 Certificate** to use when finishing configuring Okta SAML in Infisical.
![SAML Okta IdP values](../../../images/sso/okta/idp-values.png)
Back in Infisical, set **Identity Provider Single Sign-On URL**, **Identity Provider Issuer**,
and **Certificate** to **X.509 Certificate** from above. Once you've done that, press **Update** to complete the required configuration.
![SAML Okta paste values into Infisical](../../../images/sso/okta/idp-values-2.png)
8. Finally, navigate to the **Assignments** tab and select **Assign**
You can assign access to the application on a user-by-user basis using the Assign to People option, or in-bulk using the Assign to Groups option.
![SAML Okta assignment](../../../images/sso/okta/assignment.png)
At this point, you have configured everything you need within the context of the Okta Admin Portal.
9. Return to Infisical and enable SAML SSO.
Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Okta.
![SAML Okta assignment](../../../images/sso/okta/enable-saml.png)
![SAML Okta assignment](../../../images/sso/okta/enable-saml.png)
</Step>
</Steps>
<Note>
If you're configuring SAML SSO on a self-hosted instance of Infisical, make sure to
set the `JWT_PROVIDER_AUTH_SECRET` and `SITE_URL` environment variable for it to work:
set the `AUTH_SECRET` and `SITE_URL` environment variable for it to work:
- `JWT_PROVIDER_AUTH_SECRET`: This is secret key used for signing and verifying JWT. This could be a randomly-generated 256-bit hex string.
- `AUTH_SECRET`: A secret key used for signing and verifying JWT. This can be a random 32-byte base64 string generated with `openssl rand -base64 32`.
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
</Note>

Binary file not shown.

After

(image error) Size: 621 KiB

Binary file not shown.

After

(image error) Size: 437 KiB

Binary file not shown.

After

(image error) Size: 399 KiB

Binary file not shown.

After

(image error) Size: 509 KiB

Binary file not shown.

After

(image error) Size: 606 KiB

Some files were not shown because too many files have changed in this diff Show More