mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 02:27:37 +00:00
Merge remote-tracking branch 'origin' into azure-auth
This commit is contained in:
@@ -8,7 +8,7 @@ import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
|||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { PermissionSchema, SanitizedIdentityPrivilegeSchema } from "@app/server/routes/sanitizedSchemas";
|
import { ProjectPermissionSchema, SanitizedIdentityPrivilegeSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
|
export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
|
||||||
@@ -39,7 +39,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||||
permissions: PermissionSchema.array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions)
|
permissions: ProjectPermissionSchema.array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -90,7 +90,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||||
permissions: PermissionSchema.array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions),
|
permissions: ProjectPermissionSchema.array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions),
|
||||||
temporaryMode: z
|
temporaryMode: z
|
||||||
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
|
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
|
||||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
|
||||||
@@ -155,7 +155,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
message: "Slug must be a valid slug"
|
message: "Slug must be a valid slug"
|
||||||
})
|
})
|
||||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.newSlug),
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.newSlug),
|
||||||
permissions: PermissionSchema.array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.permissions),
|
permissions: ProjectPermissionSchema.array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.permissions),
|
||||||
isTemporary: z.boolean().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
|
isTemporary: z.boolean().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
|
||||||
temporaryMode: z
|
temporaryMode: z
|
||||||
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
|
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
|
||||||
|
|||||||
@@ -89,6 +89,9 @@ export const UNIVERSAL_AUTH = {
|
|||||||
},
|
},
|
||||||
RENEW_ACCESS_TOKEN: {
|
RENEW_ACCESS_TOKEN: {
|
||||||
accessToken: "The access token to renew."
|
accessToken: "The access token to renew."
|
||||||
|
},
|
||||||
|
REVOKE_ACCESS_TOKEN: {
|
||||||
|
accessToken: "The access token to revoke."
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { UnpackedPermissionSchema } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
import { UnpackedPermissionSchema } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
|
|
||||||
// sometimes the return data must be santizied to avoid leaking important values
|
// sometimes the return data must be santizied to avoid leaking important values
|
||||||
// always prefer pick over omit in zod
|
// always prefer pick over omit in zod
|
||||||
@@ -64,14 +65,12 @@ export const secretRawSchema = z.object({
|
|||||||
secretComment: z.string().optional()
|
secretComment: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const PermissionSchema = z.object({
|
export const ProjectPermissionSchema = z.object({
|
||||||
action: z
|
action: z
|
||||||
.string()
|
.nativeEnum(ProjectPermissionActions)
|
||||||
.min(1)
|
|
||||||
.describe("Describe what action an entity can take. Possible actions: create, edit, delete, and read"),
|
.describe("Describe what action an entity can take. Possible actions: create, edit, delete, and read"),
|
||||||
subject: z
|
subject: z
|
||||||
.string()
|
.nativeEnum(ProjectPermissionSub)
|
||||||
.min(1)
|
|
||||||
.describe("The entity this permission pertains to. Possible options: secrets, environments"),
|
.describe("The entity this permission pertains to. Possible options: secrets, environments"),
|
||||||
conditions: z
|
conditions: z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -36,4 +36,29 @@ export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvid
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/token/revoke",
|
||||||
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Revoke access token",
|
||||||
|
body: z.object({
|
||||||
|
accessToken: z.string().trim().describe(UNIVERSAL_AUTH.REVOKE_ACCESS_TOKEN.accessToken)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
await server.services.identityAccessToken.revokeAccessToken(req.body.accessToken);
|
||||||
|
return {
|
||||||
|
message: "Successfully revoked access token"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName, TIdentityAccessTokens } from "@app/db/schemas";
|
import { IdentityAuthMethod, TableName, TIdentityAccessTokens } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
|
||||||
@@ -15,23 +15,46 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
|
|||||||
const doc = await (tx || db)(TableName.IdentityAccessToken)
|
const doc = await (tx || db)(TableName.IdentityAccessToken)
|
||||||
.where(filter)
|
.where(filter)
|
||||||
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityAccessToken}.identityId`)
|
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityAccessToken}.identityId`)
|
||||||
.leftJoin(
|
.leftJoin(TableName.IdentityUaClientSecret, (qb) => {
|
||||||
TableName.IdentityUaClientSecret,
|
qb.on(`${TableName.Identity}.authMethod`, db.raw("?", [IdentityAuthMethod.Univeral])).andOn(
|
||||||
`${TableName.IdentityAccessToken}.identityUAClientSecretId`,
|
`${TableName.IdentityAccessToken}.identityUAClientSecretId`,
|
||||||
`${TableName.IdentityUaClientSecret}.id`
|
`${TableName.IdentityUaClientSecret}.id`
|
||||||
)
|
);
|
||||||
.leftJoin(
|
})
|
||||||
TableName.IdentityUniversalAuth,
|
.leftJoin(TableName.IdentityUniversalAuth, (qb) => {
|
||||||
|
qb.on(`${TableName.Identity}.authMethod`, db.raw("?", [IdentityAuthMethod.Univeral])).andOn(
|
||||||
`${TableName.IdentityUaClientSecret}.identityUAId`,
|
`${TableName.IdentityUaClientSecret}.identityUAId`,
|
||||||
`${TableName.IdentityUniversalAuth}.id`
|
`${TableName.IdentityUniversalAuth}.id`
|
||||||
)
|
);
|
||||||
|
})
|
||||||
|
.leftJoin(TableName.IdentityGcpAuth, (qb) => {
|
||||||
|
qb.on(`${TableName.Identity}.authMethod`, db.raw("?", [IdentityAuthMethod.GCP_AUTH])).andOn(
|
||||||
|
`${TableName.Identity}.id`,
|
||||||
|
`${TableName.IdentityGcpAuth}.identityId`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.leftJoin(TableName.IdentityAwsAuth, (qb) => {
|
||||||
|
qb.on(`${TableName.Identity}.authMethod`, db.raw("?", [IdentityAuthMethod.AWS_AUTH])).andOn(
|
||||||
|
`${TableName.Identity}.id`,
|
||||||
|
`${TableName.IdentityAwsAuth}.identityId`
|
||||||
|
);
|
||||||
|
})
|
||||||
.select(selectAllTableCols(TableName.IdentityAccessToken))
|
.select(selectAllTableCols(TableName.IdentityAccessToken))
|
||||||
.select(
|
.select(
|
||||||
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityUniversalAuth),
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityUniversalAuth).as("accessTokenTrustedIpsUa"),
|
||||||
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityGcpAuth).as("accessTokenTrustedIpsGcp"),
|
||||||
|
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityAwsAuth).as("accessTokenTrustedIpsAws"),
|
||||||
db.ref("name").withSchema(TableName.Identity)
|
db.ref("name").withSchema(TableName.Identity)
|
||||||
)
|
)
|
||||||
.first();
|
.first();
|
||||||
return doc;
|
|
||||||
|
if (!doc) return;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...doc,
|
||||||
|
accessTokenTrustedIps:
|
||||||
|
doc.accessTokenTrustedIpsUa || doc.accessTokenTrustedIpsGcp || doc.accessTokenTrustedIpsAws
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "IdAccessTokenFindOne" });
|
throw new DatabaseError({ error, name: "IdAccessTokenFindOne" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,24 @@ export const identityAccessTokenServiceFactory = ({
|
|||||||
return { accessToken, identityAccessToken: updatedIdentityAccessToken };
|
return { accessToken, identityAccessToken: updatedIdentityAccessToken };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const revokeAccessToken = async (accessToken: string) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
const decodedToken = jwt.verify(accessToken, appCfg.AUTH_SECRET) as JwtPayload & {
|
||||||
|
identityAccessTokenId: string;
|
||||||
|
};
|
||||||
|
if (decodedToken.authTokenType !== AuthTokenType.IDENTITY_ACCESS_TOKEN) throw new UnauthorizedError();
|
||||||
|
|
||||||
|
const identityAccessToken = await identityAccessTokenDAL.findOne({
|
||||||
|
[`${TableName.IdentityAccessToken}.id` as "id"]: decodedToken.identityAccessTokenId,
|
||||||
|
isAccessTokenRevoked: false
|
||||||
|
});
|
||||||
|
if (!identityAccessToken) throw new UnauthorizedError();
|
||||||
|
|
||||||
|
const revokedToken = await identityAccessTokenDAL.deleteById(identityAccessToken.id);
|
||||||
|
return { revokedToken };
|
||||||
|
};
|
||||||
|
|
||||||
const fnValidateIdentityAccessToken = async (token: TIdentityAccessTokenJwtPayload, ipAddress?: string) => {
|
const fnValidateIdentityAccessToken = async (token: TIdentityAccessTokenJwtPayload, ipAddress?: string) => {
|
||||||
const identityAccessToken = await identityAccessTokenDAL.findOne({
|
const identityAccessToken = await identityAccessTokenDAL.findOne({
|
||||||
[`${TableName.IdentityAccessToken}.id` as "id"]: token.identityAccessTokenId,
|
[`${TableName.IdentityAccessToken}.id` as "id"]: token.identityAccessTokenId,
|
||||||
@@ -132,5 +150,5 @@ export const identityAccessTokenServiceFactory = ({
|
|||||||
return { ...identityAccessToken, orgId: identityOrgMembership.orgId };
|
return { ...identityAccessToken, orgId: identityOrgMembership.orgId };
|
||||||
};
|
};
|
||||||
|
|
||||||
return { renewAccessToken, fnValidateIdentityAccessToken };
|
return { renewAccessToken, revokeAccessToken, fnValidateIdentityAccessToken };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -462,27 +462,39 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
ssm.config.update(config);
|
ssm.config.update(config);
|
||||||
|
|
||||||
const metadata = z.record(z.any()).parse(integration.metadata || {});
|
const metadata = z.record(z.any()).parse(integration.metadata || {});
|
||||||
|
const awsParameterStoreSecretsObj: Record<string, AWS.SSM.Parameter> = {};
|
||||||
|
|
||||||
const params = {
|
// now fetch all aws parameter store secrets
|
||||||
|
let hasNext = true;
|
||||||
|
let nextToken: string | undefined;
|
||||||
|
while (hasNext) {
|
||||||
|
const parameters = await ssm
|
||||||
|
.getParametersByPath({
|
||||||
Path: integration.path as string,
|
Path: integration.path as string,
|
||||||
Recursive: false,
|
Recursive: false,
|
||||||
WithDecryption: true
|
WithDecryption: true,
|
||||||
};
|
MaxResults: 10,
|
||||||
|
NextToken: nextToken
|
||||||
|
})
|
||||||
|
.promise();
|
||||||
|
|
||||||
const parameterList = (await ssm.getParametersByPath(params).promise()).Parameters;
|
if (parameters.Parameters) {
|
||||||
|
parameters.Parameters.forEach((parameter) => {
|
||||||
|
if (parameter.Name) {
|
||||||
|
const secKey = parameter.Name.substring((integration.path as string).length);
|
||||||
|
awsParameterStoreSecretsObj[secKey] = parameter;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
hasNext = Boolean(parameters.NextToken);
|
||||||
|
nextToken = parameters.NextToken;
|
||||||
|
}
|
||||||
|
|
||||||
const awsParameterStoreSecretsObj = (parameterList || [])
|
|
||||||
.filter(({ Name }) => Boolean(Name))
|
|
||||||
.reduce(
|
|
||||||
(obj, secret) => ({
|
|
||||||
...obj,
|
|
||||||
[(secret.Name as string).substring((integration.path as string).length)]: secret
|
|
||||||
}),
|
|
||||||
{} as Record<string, AWS.SSM.Parameter>
|
|
||||||
);
|
|
||||||
// Identify secrets to create
|
// Identify secrets to create
|
||||||
await Promise.all(
|
// don't use Promise.all() and promise map here
|
||||||
Object.keys(secrets).map(async (key) => {
|
// it will cause rate limit
|
||||||
|
for (const key in secrets) {
|
||||||
|
if (Object.hasOwn(secrets, key)) {
|
||||||
if (!(key in awsParameterStoreSecretsObj)) {
|
if (!(key in awsParameterStoreSecretsObj)) {
|
||||||
// case: secret does not exist in AWS parameter store
|
// case: secret does not exist in AWS parameter store
|
||||||
// -> create secret
|
// -> create secret
|
||||||
@@ -517,13 +529,16 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
})
|
})
|
||||||
.promise();
|
.promise();
|
||||||
}
|
}
|
||||||
})
|
|
||||||
);
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 50);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!metadata.shouldDisableDelete) {
|
if (!metadata.shouldDisableDelete) {
|
||||||
// Identify secrets to delete
|
for (const key in awsParameterStoreSecretsObj) {
|
||||||
await Promise.all(
|
if (Object.hasOwn(awsParameterStoreSecretsObj, key)) {
|
||||||
Object.keys(awsParameterStoreSecretsObj).map(async (key) => {
|
|
||||||
if (!(key in secrets)) {
|
if (!(key in secrets)) {
|
||||||
// case:
|
// case:
|
||||||
// -> delete secret
|
// -> delete secret
|
||||||
@@ -533,8 +548,11 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
})
|
})
|
||||||
.promise();
|
.promise();
|
||||||
}
|
}
|
||||||
})
|
await new Promise((resolve) => {
|
||||||
);
|
setTimeout(resolve, 50);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Revoke Access Token"
|
||||||
|
openapi: "POST /api/v1/auth/token/revoke"
|
||||||
|
---
|
||||||
@@ -128,6 +128,12 @@ infisical export --template=<path to template>
|
|||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
|
<Accordion title="--include-imports">
|
||||||
|
By default imported secrets are available, you can disable it by setting this option to false.
|
||||||
|
|
||||||
|
Default value: `true`
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="--format">
|
<Accordion title="--format">
|
||||||
Format of the output file. Accepted values: `dotenv`, `dotenv-export`, `csv`, `json` and `yaml`
|
Format of the output file. Accepted values: `dotenv`, `dotenv-export`, `csv`, `json` and `yaml`
|
||||||
|
|
||||||
|
|||||||
@@ -126,6 +126,12 @@ $ infisical run -- npm run dev
|
|||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
|
<Accordion title="--include-imports">
|
||||||
|
By default imported secrets are available, you can disable it by setting this option to false.
|
||||||
|
|
||||||
|
Default value: `true`
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
{" "}
|
{" "}
|
||||||
|
|
||||||
<Accordion title="--env">
|
<Accordion title="--env">
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ If none of the available stores work for you, you can try using the `file` store
|
|||||||
If you are still experiencing trouble, please seek support.
|
If you are still experiencing trouble, please seek support.
|
||||||
|
|
||||||
[Learn more about vault command](./commands/vault)
|
[Learn more about vault command](./commands/vault)
|
||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="Can I fetch secrets with Infisical if I am offline?">
|
<Accordion title="Can I fetch secrets with Infisical if I am offline?">
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ access the Infisical API using the GCP ID Token authentication method.
|
|||||||
<CodeGroup>
|
<CodeGroup>
|
||||||
```bash curl
|
```bash curl
|
||||||
curl -H "Metadata-Flavor: Google" \
|
curl -H "Metadata-Flavor: Google" \
|
||||||
'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=<identityId>'
|
'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=<identityId>&format=full'
|
||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -417,7 +417,8 @@
|
|||||||
"api-reference/endpoints/universal-auth/create-client-secret",
|
"api-reference/endpoints/universal-auth/create-client-secret",
|
||||||
"api-reference/endpoints/universal-auth/list-client-secrets",
|
"api-reference/endpoints/universal-auth/list-client-secrets",
|
||||||
"api-reference/endpoints/universal-auth/revoke-client-secret",
|
"api-reference/endpoints/universal-auth/revoke-client-secret",
|
||||||
"api-reference/endpoints/universal-auth/renew-access-token"
|
"api-reference/endpoints/universal-auth/renew-access-token",
|
||||||
|
"api-reference/endpoints/universal-auth/revoke-access-token"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user