diff --git a/.env.example b/.env.example index 1926bcef6..d3ec397de 100644 --- a/.env.example +++ b/.env.example @@ -47,11 +47,13 @@ CLIENT_ID_VERCEL= CLIENT_ID_NETLIFY= CLIENT_ID_GITHUB= CLIENT_ID_GITLAB= +CLIENT_ID_BITBUCKET= CLIENT_SECRET_HEROKU= CLIENT_SECRET_VERCEL= CLIENT_SECRET_NETLIFY= CLIENT_SECRET_GITHUB= CLIENT_SECRET_GITLAB= +CLIENT_SECRET_BITBUCKET= CLIENT_SLUG_VERCEL= # Sentry (optional) for monitoring errors diff --git a/.github/workflows/release-standalone-docker-img.yml b/.github/workflows/release-standalone-docker-img.yml index 84a0aa72e..5d91bd59c 100644 --- a/.github/workflows/release-standalone-docker-img.yml +++ b/.github/workflows/release-standalone-docker-img.yml @@ -1,11 +1,17 @@ name: Release standalone docker image -on: [workflow_dispatch] +on: + push: + tags: + - "infisical/v*.*.*" jobs: infisical-standalone: name: Build infisical standalone image runs-on: ubuntu-latest steps: + - name: Extract version from tag + id: extract_version + run: echo "::set-output name=version::${GITHUB_REF_NAME#infisical/}" - name: ☁️ Checkout source uses: actions/checkout@v3 with: @@ -64,5 +70,6 @@ jobs: tags: | infisical/infisical:latest infisical/infisical:${{ steps.commit.outputs.short }} + infisical/infisical:${{ steps.extract_version.outputs.version }} platforms: linux/amd64,linux/arm64 file: Dockerfile.standalone-infisical diff --git a/.github/workflows/release_docker_k8_operator.yaml b/.github/workflows/release_docker_k8_operator.yaml index 788d414b6..517549ea8 100644 --- a/.github/workflows/release_docker_k8_operator.yaml +++ b/.github/workflows/release_docker_k8_operator.yaml @@ -1,10 +1,16 @@ -name: Release Docker image for K8 operator -on: [workflow_dispatch] +name: Release Docker image for K8 operator +on: + push: + tags: + - "infisical-k8-operator/v*.*.*" jobs: release: runs-on: ubuntu-latest steps: + - name: Extract version from tag + id: extract_version + run: echo "::set-output name=version::${GITHUB_REF_NAME#infisical-k8-operator/}" - uses: actions/checkout@v2 - name: 🔧 Set up QEMU @@ -26,4 +32,6 @@ jobs: context: k8-operator push: true platforms: linux/amd64,linux/arm64 - tags: infisical/kubernetes-operator:latest \ No newline at end of file + tags: | + infisical/kubernetes-operator:latest + infisical/kubernetes-operator:${{ steps.extract_version.outputs.version }} diff --git a/README.md b/README.md index c5a0d3abc..b0b964d37 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@
You are receiving this notification because one or more secret leaks have been detected in a recent commit pushed + by {{pusher_name}} ({{pusher_email}}). If + these are test secrets, please add `infisical-scan:ignore` at the end of the line containing the secret as comment + in the given programming. This will prevent future notifications from being sent out for those secret(s).
+ +If these are production secrets, please rotate them immediately.
+ +Once you have taken action, be sure to update the status of the risk in your Infisical + dashboard.
+ + + \ No newline at end of file diff --git a/backend/src/types/express/index.d.ts b/backend/src/types/express/index.d.ts index f3b2700da..c7713a327 100644 --- a/backend/src/types/express/index.d.ts +++ b/backend/src/types/express/index.d.ts @@ -20,6 +20,7 @@ declare global { workspace: any; membership: any; targetMembership: any; + isUserCompleted: boolean; providerAuthToken: any; organization: any; membershipOrg: any; diff --git a/backend/src/utils/aes-gcm.ts b/backend/src/utils/aes-gcm.ts index 4457616a6..21734611b 100644 --- a/backend/src/utils/aes-gcm.ts +++ b/backend/src/utils/aes-gcm.ts @@ -4,8 +4,6 @@ const ALGORITHM = "aes-256-gcm"; const BLOCK_SIZE_BYTES = 16; export default class AesGCM { - constructor() {} - static encrypt( text: string, secret: string diff --git a/backend/src/utils/auth.ts b/backend/src/utils/auth.ts index 6970292c4..dccd5b5e4 100644 --- a/backend/src/utils/auth.ts +++ b/backend/src/utils/auth.ts @@ -1,11 +1,14 @@ import express from "express"; import passport from "passport"; +import { Types } from "mongoose"; import { AuthData } from "../interfaces/middleware"; import { AuthProvider, + MembershipOrg, + Organization, ServiceAccount, ServiceTokenData, - User, + User } from "../models"; import { createToken } from "../helpers/auth"; import { @@ -14,11 +17,15 @@ import { getJwtProviderAuthLifetime, getJwtProviderAuthSecret, } from "../config"; +import { getSSOConfigHelper } from "../ee/helpers/organizations"; +import { InternalServerError, OrganizationNotFoundError } from "./errors"; +import { INVITED, MEMBER } from "../variables"; +import { getSiteURL } from "../config"; // eslint-disable-next-line @typescript-eslint/no-var-requires const GoogleStrategy = require("passport-google-oauth20").Strategy; - -// TODO: find a more optimal folder structure to store these types of functions +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { MultiSamlStrategy } = require("@node-saml/passport-saml"); /** * Returns an object containing the id of the authentication data payload @@ -39,7 +46,6 @@ const getAuthDataPayloadIdObj = (authData: AuthData) => { } }; - /** * Returns an object containing the user associated with the authentication data payload * @param {AuthData} authData - authentication data object @@ -56,7 +62,7 @@ const getAuthDataPayloadUserObj = (authData: AuthData) => { } if (authData.authPayload instanceof ServiceTokenData) { - return { user: authData.authPayload.user }; + return { user: authData.authPayload.user };0 } } @@ -68,47 +74,143 @@ const initializePassport = async () => { passReqToCallback: true, clientID: googleClientId, clientSecret: googleClientSecret, - callbackURL: "/api/v1/auth/callback/google", + callbackURL: "/api/v1/sso/google", scope: ["profile", " email"], }, async ( req: express.Request, accessToken: string, refreshToken: string, profile: any, - cb: any + done: any ) => { try { const email = profile.emails[0].value; + const firstName = profile.name.givenName; + const lastName = profile.name.familyName; + let user = await User.findOne({ - authProvider: AuthProvider.GOOGLE, - authId: profile.id, - }).select("+publicKey") + email + }).select("+publicKey"); + + if (user && user.authProvider !== AuthProvider.GOOGLE) { + done(InternalServerError()); + } if (!user) { user = await new User({ email, authProvider: AuthProvider.GOOGLE, authId: profile.id, + firstName, + lastName }).save(); } + const isUserCompleted = !!user.publicKey; const providerAuthToken = createToken({ payload: { userId: user._id.toString(), email: user.email, + firstName, + lastName, authProvider: user.authProvider, - isUserCompleted: !!user.publicKey, + isUserCompleted }, expiresIn: await getJwtProviderAuthLifetime(), secret: await getJwtProviderAuthSecret(), }); + req.isUserCompleted = isUserCompleted; req.providerAuthToken = providerAuthToken; - cb(null, profile); + done(null, profile); } catch (err) { - cb(null, false); + done(null, false); } })); + + passport.use("saml", new MultiSamlStrategy( + { + passReqToCallback: true, + getSamlOptions: async (req: any, done: any) => { + const { ssoIdentifier } = req.params; + + const ssoConfig = await getSSOConfigHelper({ + ssoConfigId: new Types.ObjectId(ssoIdentifier) + }); + + const samlConfig = ({ + path: "/api/v1/auth/callback/saml", + callbackURL: `${await getSiteURL()}/api/v1/auth/callback/saml`, + entryPoint: ssoConfig.entryPoint, + issuer: ssoConfig.issuer, + cert: ssoConfig.cert, + audience: ssoConfig.audience + }); + + req.ssoConfig = ssoConfig; + + done(null, samlConfig); + }, + }, + async (req: any, profile: any, done: any) => { + + if (!req.ssoConfig.isActive) return done(InternalServerError()); + + 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; + + let user = await User.findOne({ + email + }).select("+publicKey"); + + if (user && user.authProvider !== AuthProvider.OKTA_SAML) { + done(InternalServerError()); + } + + if (!user) { + user = await new User({ + email, + authProvider: AuthProvider.OKTA_SAML, + authId: profile.id, + firstName, + lastName + }).save(); + + await new MembershipOrg({ + inviteEmail: email, + user: user._id, + organization: organization?._id, + role: MEMBER, + status: INVITED + }).save(); + } + + const isUserCompleted = !!user.publicKey; + const providerAuthToken = createToken({ + payload: { + userId: user._id.toString(), + email: user.email, + firstName, + lastName, + organizationName: organization?.name, + authProvider: user.authProvider, + isUserCompleted + }, + expiresIn: await getJwtProviderAuthLifetime(), + secret: await getJwtProviderAuthSecret(), + }); + + req.isUserCompleted = isUserCompleted; + req.providerAuthToken = providerAuthToken; + + done(null, profile); + } + )); } export { diff --git a/backend/src/utils/errors.ts b/backend/src/utils/errors.ts index f99ef27b2..42cc20509 100644 --- a/backend/src/utils/errors.ts +++ b/backend/src/utils/errors.ts @@ -27,7 +27,7 @@ export const UnauthorizedRequestError = (error?: Partial
-Some more examples of referencing are
+Secret referencing relies on interpolation syntax. This syntax allows you to reference a secret in any environment or [folder](./folder).
-| Syntax | Environment | Folder | Secret Key |
+To reference a secret named 'mysecret' in the same [folder](./folder) and environment, you'd use `${mysecret}`.
+However, to reference the same secret at the root of a different environment, for instance `dev` environment, you'd use `${dev.mysecret}`.
+
+Here are a few more examples to help you understand how to reference secrets in different contexts:
+
+| Reference syntax | Environment | Folder | Secret Key |
| --------------------- | ----------- | ------------ | ---------- |
-| `${KEY1}` | same env | ssame folder | KEY1 |
-| `${dev.KEY2}` | dev | / | KEY2 |
-| `${test.frontend.KEY2}` | test | /frontend | KEY2 |
+| `${KEY1}` | same env | same folder | KEY1 |
+| `${dev.KEY2}` | `dev` | `/` (root of dev environment) | KEY2 |
+| `${prod.frontend.KEY2}` | `prod` | `/frontend` | KEY2 |
-# Permission system for reference
+## Fetching fully constructed values
-When you use the infisical CLI to log in, the permission system will work the same way as your user permissions.
-This means that if you have permission to access other environments, your references to those environments will be resolved.
+Secret referencing combines multiple secrets into one unified value, reconstructed only on the client side. To retrieve this value, you need access to read the environment and [folder](./folder) from where the secrets originate.
+For instance, to access a secret 'A' composed of secrets 'B' and 'C' from different environments, you must have read access to both 'A' and 'B'
-When using the Infisical CLI with a service token, the service token must have permissions to the referenced environment and folder path.
+When using [service tokens](./token) to fetch referenced secrets, ensure the service token has read access to all referenced environments and folders.
+Without proper permissions, the final secret value may be incomplete.
+
+## Import entire folders
+
+While secret referencing effectively minimizes duplication, there might be instances where you need to import or replicate an entire folder's secrets into another. This can be achieved using the 'Import' feature.
+
+This feature allows you to link secrets from one environment/folder into another environment/folder. It proves beneficial when you have common secrets that need to be available across multiple environments/folders.
+
+To add an import, simply click on the `Add import` button and provide the environment and secret path from where the secrets should be imported.
+
+
+
+The hierarchy of importing secrets is governed by a "last-one-wins" rule. This means the sequence in which you import matters - the final folder imported will override secrets from any prior folders.
+Moreover, any secrets you define directly in your environment will take precedence over secrets from any imported folders.
+
+You can modify this sequence by dragging and rearranging the folders using the `Change Order` drag handle.
+
+
diff --git a/docs/documentation/platform/token.mdx b/docs/documentation/platform/token.mdx
index 1fecb2fef..1371d4621 100644
--- a/docs/documentation/platform/token.mdx
+++ b/docs/documentation/platform/token.mdx
@@ -1,21 +1,37 @@
---
-title: "Infisical Token"
-description: "Use the Infisical Token as one of the authentication methods."
+title: "Service token"
+description: "Infisical service tokens allows you to programmatically interact with Infisical"
---
-An Infisical Token is useful for:
+Service tokens play an integral role in allowing programmatic interactions with an Infisical project, functioning as digital token that open access to specific project resources such as secrets.
-- Authenticating the [Infisical CLI](/cli/overview) when there isn't an easy way to input your login credentials.
-- Granting the [Infisical SDKs](/sdks/overview) access to secrets scoped to a project and environment.
+When you generate a service token, you can define its access level, not only by specifying the paths and environments it can interact with, but also by determining the level of mutation it can perform, such as read-only, write, or both.
-It's also useful for CI/CD environments and integrations such as [Docker](/integrations/platforms/docker) and [Docker Compose](/integrations/platforms/docker-compose).
+This level of control not only ensures maximum flexibility but also significantly enhances security as it allows you to define fine grained access to project resources.
-To generate the the token, head over to your project settings as shown below. On creating a service token you can scope it to a path to limit the access.
+
+## Creating a service token
+
+To generate the token, head over to your project settings as shown below. On creating a service token you can scope it to a path to limit the access.

-## Feeding Infisical Token to the CLI
+### Service token permissions
+
-The Infisical CLI checks for the presence of an environment variable called `INFISICAL_TOKEN`.
-If it detects this variable in the terminal where it is being run, it will use it to authenticate and retrieve the environment variables that the token is authorized to access.
-This allows you to use the CLI in environments where you are unable to run the `infisical login` command.
+
+Service tokens can be scoped to multiple environments and paths. To add a new permission, choose the environment you want to give access to and then choose the path you'd like to give access to within that environment.
+
+Permissions for paths are powered by [Glob pattern](https://www.malikbrowne.com/blog/a-beginners-guide-glob-patterns/). This means you can create advanced folder permissions with a simple Glob patterns.
+
+**Examples of common Glob pattens**
+
+