mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 11:44:31 +00:00
Update permission implementation for ST V3
This commit is contained in:
@@ -9,6 +9,7 @@ JWT_SIGNUP_SECRET=3679e04ca949f914c03332aaaeba805a
|
||||
JWT_REFRESH_SECRET=5f2f3c8f0159068dc2bbb3a652a716ff
|
||||
JWT_AUTH_SECRET=4be6ba5602e0fa0ac6ac05c3cd4d247f
|
||||
JWT_SERVICE_SECRET=f32f716d70a42c5703f4656015e76200
|
||||
JWT_SERVICE_TOKEN_SECRET=f32f716d70a42c5703f4656015e76200
|
||||
JWT_PROVIDER_AUTH_SECRET=f32f716d70a42c5703f4656015e76201
|
||||
|
||||
# JWT lifetime
|
||||
|
||||
@@ -110,14 +110,14 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath,
|
||||
acceptedPermissions: [Permission.READ, Permission.READ_WRITE]
|
||||
requiredPermissions: [Permission.READ]
|
||||
});
|
||||
permissionCheckFn = (env: string, secPath: string) =>
|
||||
isValidScopeV3({
|
||||
authPayload: req.authData.authPayload as IServiceTokenDataV3,
|
||||
environment: env,
|
||||
secretPath: secPath,
|
||||
acceptedPermissions: [Permission.READ, Permission.READ_WRITE]
|
||||
requiredPermissions: [Permission.READ]
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -213,7 +213,7 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath,
|
||||
acceptedPermissions: [Permission.READ, Permission.READ_WRITE]
|
||||
requiredPermissions: [Permission.READ]
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -285,7 +285,7 @@ export const createSecretRaw = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath,
|
||||
acceptedPermissions: [Permission.READ_WRITE]
|
||||
requiredPermissions: [Permission.WRITE]
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -384,7 +384,7 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath,
|
||||
acceptedPermissions: [Permission.READ_WRITE]
|
||||
requiredPermissions: [Permission.WRITE]
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -464,7 +464,7 @@ export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath,
|
||||
acceptedPermissions: [Permission.READ_WRITE]
|
||||
requiredPermissions: [Permission.WRITE]
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -559,14 +559,14 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath,
|
||||
acceptedPermissions: [Permission.READ, Permission.READ_WRITE]
|
||||
requiredPermissions: [Permission.READ]
|
||||
});
|
||||
permissionCheckFn = (env: string, secPath: string) =>
|
||||
isValidScopeV3({
|
||||
authPayload: req.authData.authPayload as IServiceTokenDataV3,
|
||||
environment: env,
|
||||
secretPath: secPath,
|
||||
acceptedPermissions: [Permission.READ, Permission.READ_WRITE]
|
||||
requiredPermissions: [Permission.READ]
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -644,7 +644,7 @@ export const getSecretByName = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath,
|
||||
acceptedPermissions: [Permission.READ, Permission.READ_WRITE]
|
||||
requiredPermissions: [Permission.READ]
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -717,7 +717,7 @@ export const createSecret = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath,
|
||||
acceptedPermissions: [Permission.READ_WRITE]
|
||||
requiredPermissions: [Permission.WRITE]
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -816,7 +816,7 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath,
|
||||
acceptedPermissions: [Permission.READ_WRITE]
|
||||
requiredPermissions: [Permission.WRITE]
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -892,7 +892,7 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath,
|
||||
acceptedPermissions: [Permission.READ_WRITE]
|
||||
requiredPermissions: [Permission.WRITE]
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -950,7 +950,7 @@ export const createSecretByNameBatch = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath,
|
||||
acceptedPermissions: [Permission.READ_WRITE]
|
||||
requiredPermissions: [Permission.WRITE]
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -999,7 +999,7 @@ export const updateSecretByNameBatch = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath,
|
||||
acceptedPermissions: [Permission.READ_WRITE]
|
||||
requiredPermissions: [Permission.WRITE]
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -1048,7 +1048,7 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath,
|
||||
acceptedPermissions: [Permission.READ_WRITE]
|
||||
requiredPermissions: [Permission.WRITE]
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
user,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
publicKey,
|
||||
usageCount: 0,
|
||||
scopes,
|
||||
isActive,
|
||||
expiresAt
|
||||
|
||||
@@ -64,7 +64,7 @@ class EELicenseService {
|
||||
secretVersioning: true,
|
||||
pitRecovery: false,
|
||||
ipAllowlisting: false,
|
||||
rbac: true,
|
||||
rbac: false,
|
||||
customRateLimits: false,
|
||||
customAlerts: false,
|
||||
auditLogs: false,
|
||||
|
||||
@@ -249,7 +249,8 @@ export const getAuthSTDPayload = async ({
|
||||
isActive: true
|
||||
},
|
||||
{
|
||||
lastUsed: new Date()
|
||||
lastUsed: new Date(),
|
||||
$inc: { usageCount: 1 }
|
||||
},
|
||||
{
|
||||
new: true
|
||||
|
||||
@@ -69,12 +69,12 @@ export const isValidScopeV3 = ({
|
||||
authPayload,
|
||||
environment,
|
||||
secretPath,
|
||||
acceptedPermissions
|
||||
requiredPermissions
|
||||
}: {
|
||||
authPayload: IServiceTokenDataV3,
|
||||
environment: string,
|
||||
secretPath: string,
|
||||
acceptedPermissions: Permission[]
|
||||
requiredPermissions: Permission[]
|
||||
}) => {
|
||||
const { scopes } = authPayload;
|
||||
|
||||
@@ -84,7 +84,7 @@ export const isValidScopeV3 = ({
|
||||
scope.environment === environment
|
||||
);
|
||||
|
||||
if (validScope && !acceptedPermissions.includes(validScope.permission)) {
|
||||
if (validScope && !requiredPermissions.every(permission => validScope.permissions.includes(permission))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@ import { Document, Schema, Types, model } from "mongoose";
|
||||
|
||||
export enum Permission {
|
||||
READ = "read",
|
||||
READ_WRITE = "readWrite"
|
||||
WRITE = "write"
|
||||
}
|
||||
|
||||
export interface Scope {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
permission: Permission;
|
||||
permissions: Permission[];
|
||||
}
|
||||
|
||||
export interface IServiceTokenDataV3 extends Document {
|
||||
@@ -19,6 +19,7 @@ export interface IServiceTokenDataV3 extends Document {
|
||||
publicKey: string;
|
||||
isActive: boolean;
|
||||
lastUsed?: Date;
|
||||
usageCount: number;
|
||||
expiresAt?: Date;
|
||||
scopes: Array<Scope>;
|
||||
}
|
||||
@@ -51,6 +52,11 @@ const serviceTokenDataV3Schema = new Schema(
|
||||
type: Date,
|
||||
required: false
|
||||
},
|
||||
usageCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
required: true
|
||||
},
|
||||
expiresAt: {
|
||||
type: Date,
|
||||
required: false,
|
||||
@@ -68,9 +74,10 @@ const serviceTokenDataV3Schema = new Schema(
|
||||
default: "/",
|
||||
required: true
|
||||
},
|
||||
permission: {
|
||||
type: String,
|
||||
enum: [Permission.READ, Permission.READ_WRITE],
|
||||
permissions: {
|
||||
type: [String],
|
||||
enum: [Permission.READ, Permission.WRITE],
|
||||
default: [Permission.READ],
|
||||
required: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,13 +20,13 @@ import { isValidScopeV3 } from "../helpers";
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath = "/",
|
||||
acceptedPermissions
|
||||
requiredPermissions
|
||||
}: {
|
||||
serviceTokenData: IServiceTokenDataV3;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
secretPath?: string;
|
||||
acceptedPermissions: Permission[];
|
||||
requiredPermissions: Permission[];
|
||||
}) => {
|
||||
|
||||
if (!serviceTokenData.workspace.equals(workspaceId)) {
|
||||
@@ -41,7 +41,7 @@ import { isValidScopeV3 } from "../helpers";
|
||||
authPayload: serviceTokenData,
|
||||
environment,
|
||||
secretPath,
|
||||
acceptedPermissions
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
if (!isValid) throw UnauthorizedRequestError({
|
||||
@@ -57,7 +57,7 @@ export const CreateServiceTokenV3 = z.object({
|
||||
publicKey: z.string().trim(),
|
||||
scopes: z
|
||||
.object({
|
||||
permission: z.enum(["read", "readWrite"]),
|
||||
permissions: z.enum(["read", "write"]).array(),
|
||||
environment: z.string().trim(),
|
||||
secretPath: z.string().trim()
|
||||
})
|
||||
@@ -78,7 +78,7 @@ export const UpdateServiceTokenV3 = z.object({
|
||||
isActive: z.boolean().optional(),
|
||||
scopes: z
|
||||
.object({
|
||||
permission: z.enum(["read", "readWrite"]),
|
||||
permissions: z.enum(["read", "write"]).array(),
|
||||
environment: z.string().trim(),
|
||||
secretPath: z.string().trim()
|
||||
})
|
||||
|
||||
4
frontend/src/hooks/api/serviceTokens/enums.ts
Normal file
4
frontend/src/hooks/api/serviceTokens/enums.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum Permission {
|
||||
READ = "read",
|
||||
WRITE = "write"
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Permission } from "./enums";
|
||||
|
||||
export type ServiceTokenScope = {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
@@ -37,7 +39,7 @@ export type DeleteServiceTokenRes = { serviceTokenData: ServiceToken };
|
||||
// --- v3
|
||||
|
||||
export type ServiceTokenV3Scope = {
|
||||
permission: string;
|
||||
permissions: Permission[];
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
};
|
||||
@@ -48,6 +50,7 @@ export type ServiceTokenDataV3 = {
|
||||
workspace: string;
|
||||
isActive: boolean;
|
||||
lastUsed?: string;
|
||||
usageCount: number;
|
||||
scopes: ServiceTokenV3Scope[];
|
||||
expiresAt?: string;
|
||||
createdAt: string;
|
||||
|
||||
@@ -32,6 +32,9 @@ import {
|
||||
useGetUserWsKey,
|
||||
useUpdateServiceTokenV3
|
||||
} from "@app/hooks/api";
|
||||
import {
|
||||
Permission
|
||||
} from "@app/hooks/api/serviceTokens/enums";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const expirations = [
|
||||
@@ -43,7 +46,13 @@ const expirations = [
|
||||
{ label: "12 months", value: "31104000" }
|
||||
];
|
||||
|
||||
const permissionValues: Array<"read" | "readWrite"> = ["read", "readWrite"];
|
||||
const permissionsMap: {
|
||||
"read": Permission[],
|
||||
"readWrite": Permission[]
|
||||
} = {
|
||||
"read": [Permission.READ],
|
||||
"readWrite": [Permission.READ, Permission.WRITE],
|
||||
}
|
||||
|
||||
const schema = yup.object({
|
||||
name: yup.string().required("ST V3 name is required"),
|
||||
@@ -51,7 +60,7 @@ const schema = yup.object({
|
||||
scopes: yup
|
||||
.array(
|
||||
yup.object({
|
||||
permission: yup.string().oneOf(permissionValues, "Invalid permission").required().label("Permission"),
|
||||
permission: yup.string().oneOf(Object.keys(permissionsMap), "Invalid permission").required().label("Permission") as yup.SchemaOf<"read" | "readWrite", object>,
|
||||
environment: yup.string().max(50).required().label("Environment"),
|
||||
secretPath: yup
|
||||
.string()
|
||||
@@ -112,7 +121,18 @@ export const AddServiceTokenV3Modal = ({
|
||||
if (serviceTokenData) {
|
||||
reset({
|
||||
name: serviceTokenData.name,
|
||||
scopes: serviceTokenData.scopes
|
||||
scopes: serviceTokenData.scopes.map((scope) => {
|
||||
let permission = "read";
|
||||
if (scope.permissions.includes(Permission.WRITE)) {
|
||||
permission = "readWrite";
|
||||
}
|
||||
|
||||
return ({
|
||||
environment: scope.environment,
|
||||
secretPath: scope.secretPath,
|
||||
permission
|
||||
})
|
||||
})
|
||||
});
|
||||
} else {
|
||||
reset({
|
||||
@@ -140,12 +160,21 @@ export const AddServiceTokenV3Modal = ({
|
||||
scopes: any;
|
||||
};
|
||||
|
||||
// convert read/readWrite permission => ["read", "write"] format
|
||||
const reformattedScopes = scopes.map((scope) => {
|
||||
return ({
|
||||
environment: scope.environment,
|
||||
secretPath: scope.secretPath,
|
||||
permissions: permissionsMap[scope.permission]
|
||||
});
|
||||
})
|
||||
|
||||
if (serviceTokenData) {
|
||||
// update
|
||||
await updateMutateAsync({
|
||||
serviceTokenDataId: serviceTokenData.serviceTokenDataId,
|
||||
name,
|
||||
scopes,
|
||||
scopes: reformattedScopes,
|
||||
expiresIn: expiresIn === "" ? undefined : Number(expiresIn)
|
||||
});
|
||||
} else {
|
||||
@@ -176,7 +205,7 @@ export const AddServiceTokenV3Modal = ({
|
||||
name,
|
||||
workspaceId: currentWorkspace._id,
|
||||
publicKey,
|
||||
scopes,
|
||||
scopes: reformattedScopes,
|
||||
expiresIn: expiresIn === "" ? undefined : Number(expiresIn),
|
||||
encryptedKey: ciphertext,
|
||||
nonce
|
||||
|
||||
@@ -21,9 +21,8 @@ import {
|
||||
useGetWorkspaceServiceTokenDataV3,
|
||||
useUpdateServiceTokenV3
|
||||
} from "@app/hooks/api";
|
||||
import {
|
||||
ServiceTokenV3Scope
|
||||
} from "@app/hooks/api/serviceTokens/types"
|
||||
import { ServiceTokenV3Scope } from "@app/hooks/api/serviceTokens/types"
|
||||
import { Permission } from "@app/hooks/api/serviceTokens/enums"
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
type Props = {
|
||||
@@ -90,6 +89,7 @@ export const ServiceTokenV3Table = ({
|
||||
<Th>Name</Th>
|
||||
<Th>Status</Th>
|
||||
<Th>Scopes</Th>
|
||||
<Th># Times Used</Th>
|
||||
<Th>Last Used</Th>
|
||||
<Th>Created At</Th>
|
||||
<Th>Expires At</Th>
|
||||
@@ -106,6 +106,7 @@ export const ServiceTokenV3Table = ({
|
||||
name,
|
||||
isActive,
|
||||
lastUsed,
|
||||
usageCount,
|
||||
scopes,
|
||||
createdAt,
|
||||
expiresAt
|
||||
@@ -135,16 +136,25 @@ export const ServiceTokenV3Table = ({
|
||||
</Td>
|
||||
<Td>
|
||||
{scopes.map((scope) => {
|
||||
let permissionText = "read"
|
||||
if (
|
||||
scope.permissions.includes(Permission.WRITE) &&
|
||||
scope.permissions.includes(Permission.READ)
|
||||
) {
|
||||
permissionText = "readWrite";
|
||||
}
|
||||
|
||||
return (
|
||||
<p key={`service-token-${_id}-scope-${scope.environment}-${scope.secretPath}`}>
|
||||
<span className="font-bold">
|
||||
{scope.permission}
|
||||
{scope.permissions}
|
||||
</span>
|
||||
{` @${scope.environment} - ${scope.secretPath}`}
|
||||
</p>
|
||||
);
|
||||
})}
|
||||
</Td>
|
||||
<Td>{usageCount}</Td>
|
||||
<Td>{lastUsed ? formatDate(lastUsed) : "-"}</Td>
|
||||
<Td>{formatDate(createdAt)}</Td>
|
||||
<Td>{expiresAt ? formatDate(expiresAt) : "-"}</Td>
|
||||
|
||||
Reference in New Issue
Block a user