Update MI docs for client id/secret
@@ -531,6 +531,8 @@ export const updateMachineIdentity = async (req: Request, res: Response) => {
|
|||||||
if (!machineIdentity) throw ResourceNotFoundError({
|
if (!machineIdentity) throw ResourceNotFoundError({
|
||||||
message: `Machine identity with id ${machineId} not found`
|
message: `Machine identity with id ${machineId} not found`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: validate existing role (if it is currently admin then cant demote it)
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, machineIdentity.organization.toString());
|
const { permission } = await getUserOrgPermissions(req.user._id, machineIdentity.organization.toString());
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ fetch secrets back from the `/` path of the `development` environment in some pr
|
|||||||
|
|
||||||
Here's a few pointers to get you acquainted with MIs:
|
Here's a few pointers to get you acquainted with MIs:
|
||||||
|
|
||||||
- When you create a MI, you get issued a refresh token that can be exchanged for an access token to authenticate with the Infisical API.
|
- Each MI has 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.
|
||||||
- MIs support IP allowlisting; this means you can restrict the usage of a MI access token to a specific IP or CIDR range.
|
- MIs support IP allowlisting; this means you can restrict the usage of a MI access token to a specific IP or CIDR range.
|
||||||
- MIs rely on the role-based permission system to provision access to resources like secrets.
|
- MIs rely on the role-based permission system to provision access to resources like secrets.
|
||||||
- MIs support expiration, so, if specified, the refresh token of the MI will automatically be defunct after a period of time.
|
- MIs support expiration, so, if specified, the client secret of the MI will automatically be defunct after a period of time.
|
||||||
- MIs tracks most recent usage of their refresh and access tokens; they also keeps track of each token's usage count.
|
- MIs tracks most recent usage of their client secrets and access tokens; they also keep track of each token's usage count.
|
||||||
- MIs are editable.
|
- MIs are editable.
|
||||||
|
|
||||||
## Using machine identities
|
## Using machine identities
|
||||||
@@ -32,7 +32,7 @@ In the following steps, we explore how to create and use MIs for your applicatio
|
|||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
<Step title="Creating a MI">
|
<Step title="Creating a MI">
|
||||||
To create a machine identity, head to your Organization Settings > Access Control > Machine Identities and press **Create MI**.
|
To create a machine identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -42,23 +42,36 @@ In the following steps, we explore how to create and use MIs for your applicatio
|
|||||||
|
|
||||||
- Name (required): A friendly name for the MI
|
- Name (required): A friendly name for the MI
|
||||||
- Role (required): A role from the **Organization Roles** tab to permit the MI to access certain resources.
|
- Role (required): A role from the **Organization Roles** tab to permit the MI to access certain resources.
|
||||||
- Refresh Token Expires In: The number of days from now to deactivate the MI refresh token
|
|
||||||
- Trusted IPs: The IPs or CIDR ranges that the refresh and access tokens can be used from. By default, each token is given the `0.0.0.0/0` entry representing all possible IPv4 addresses.
|
|
||||||
- Access Token TTL: The time-to-live for each acccess token in seconds.
|
- Access Token TTL: The time-to-live for each acccess token in seconds.
|
||||||
- Refresh Token Rotation: Whether or not to return a new refresh token when exchanging an existing refresh token; if enabled, the existing refresh token is invalidated upon the refresh operation.
|
- 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>
|
<Warning>
|
||||||
Restricting token usage to specific trusted IPs is a paid feature.
|
Restricting **Client Secret** and access token usage to specific trusted IPs is a paid feature.
|
||||||
|
|
||||||
If you’re using Infisical Cloud, then it is available under the Pro Tier. If you’re self-hosting Infisical, then you should contact [email protected] to purchase an enterprise license to use it.
|
If you’re using Infisical Cloud, then it is available under the Pro Tier. If you’re self-hosting Infisical, then you should contact [email protected] to purchase an enterprise license to use it.
|
||||||
</Warning>
|
</Warning>
|
||||||
|
|
||||||
Once you've created the MI, you'll be issued a refresh token for it; copy the token and keep it handy.
|
</Step>
|
||||||
|
<Step title="Creating a Client Secret">
|
||||||
|
In order to use the MI, you'll need the non-sensitive **Client ID**
|
||||||
|
of the MI 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 MI to generate a **Client Secret**
|
||||||
|
for it.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Feel free to input any (optional) details for the **Client Secret** configuration:
|
||||||
|
|
||||||
|
- Description: A description for the **Client Secret**.
|
||||||
|
- TTL: 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.
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Adding a MI to a project">
|
<Step title="Adding a MI to a project">
|
||||||
If you intend the MI access project-level resources such as secrets within a specific project, you should add it to that project.
|
To enable the MI 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 MI to and go to Project Settings > Access Control > Machine Identities and press **Add MI**.
|
To do this, head over to the project you want to add the MI to and go to Project Settings > Access Control > Machine Identities and press **Add identity**.
|
||||||
|
|
||||||
Next, select the MI you want to add to the project and the role you want to assign it.
|
Next, select the MI you want to add to the project and the role you want to assign it.
|
||||||
|
|
||||||
@@ -67,22 +80,23 @@ In the following steps, we explore how to create and use MIs for your applicatio
|
|||||||

|

|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Accessing the Infisical API with the MI">
|
<Step title="Accessing the Infisical API with the MI">
|
||||||
To access the Infisical API as the MI, you should first exchange the MI refresh token from **Step 1** for an access token
|
To access the Infisical API as the MI, you should first perform a login operation
|
||||||
by making a request to the `/api/v3/machines/me/token` endpoint.
|
that is to exchange the **Client ID** and **Client Secret** of the MI for an access token
|
||||||
|
by making a request to the `/api/v3/machines/login` endpoint.
|
||||||
|
|
||||||
#### Sample request
|
#### Sample request
|
||||||
|
|
||||||
```
|
```
|
||||||
curl --location --request POST 'http://localhost:8080/api/v3/machines/me/token' \
|
curl --location --request POST 'https://app.infisical.com/api/v3/machines/login' \
|
||||||
--header 'Content-Type: application/x-www-form-urlencoded' \
|
--header 'Content-Type: application/x-www-form-urlencoded' \
|
||||||
--data-urlencode 'refreshToken=<token>'
|
--data-urlencode 'clientSecret=...' \
|
||||||
|
--data-urlencode 'clientId=...'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Sample response
|
#### Sample response
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"refreshToken": "...",
|
|
||||||
"accessToken": "...",
|
"accessToken": "...",
|
||||||
"expiresIn": 7200,
|
"expiresIn": 7200,
|
||||||
"tokenType": "Bearer"
|
"tokenType": "Bearer"
|
||||||
@@ -92,11 +106,11 @@ In the following steps, we explore how to create and use MIs for your applicatio
|
|||||||
Next, you can use the access token to authenticate with the [Infisical API](/api-reference/overview/introduction)
|
Next, you can use the access token to authenticate with the [Infisical API](/api-reference/overview/introduction)
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
Each MI access token has a time-to-live (TLL) which you can infer from the response of the refresh token exchange;
|
Each MI 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 in the **Advanced** settings of the MI.
|
the default TTL is `7200` seconds which can be adjusted in the **Advanced** settings of the MI.
|
||||||
|
|
||||||
If a MI access token expires, it can no longer authenticate with the Infisical API. In this case,
|
If a MI access token expires, it can no longer authenticate with the Infisical API. In this case,
|
||||||
a new access token should be obtained from the refresh token exchange.
|
a new access token should be obtained from the aforementioned login operation.
|
||||||
</Note>
|
</Note>
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
@@ -113,10 +127,10 @@ In the following steps, we explore how to create and use MIs for your applicatio
|
|||||||
<Accordion title="Why is the Infisical API rejecting my machine identity credentials?">
|
<Accordion title="Why is the Infisical API rejecting my machine identity credentials?">
|
||||||
There are a few reasons for why this might happen:
|
There are a few reasons for why this might happen:
|
||||||
|
|
||||||
- The refresh/access token has expired.
|
- The client secret or access token has expired.
|
||||||
- The MI is insufficently permissioned to interact with the resources you wish to access.
|
- The MI 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.
|
- You are attempting to access a `/raw` secrets endpoint that requires your project to disable E2EE.
|
||||||
- The refresh/access token is being used from an untrusted IP.
|
- The client secret/access token is being used from an untrusted IP.
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion title="Can you provide examples for using glob patterns?">
|
<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.
|
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.
|
||||||
|
|||||||
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
@@ -27,7 +27,7 @@ export const MembersPage = withPermission(
|
|||||||
<Tab value={TabSections.Member}>People</Tab>
|
<Tab value={TabSections.Member}>People</Tab>
|
||||||
<Tab value={TabSections.MachineIdentities}>
|
<Tab value={TabSections.MachineIdentities}>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<p>App Clients</p>
|
<p>Machine Identities</p>
|
||||||
<div className="ml-2 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
<div className="ml-2 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
New
|
New
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ export const AddMachineIdentityModal = ({
|
|||||||
reset();
|
reset();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ModalContent title={`${popUp?.machineIdentity?.data ? "Update" : "Create"} App Client`}>
|
<ModalContent title={`${popUp?.machineIdentity?.data ? "Update" : "Create"} Machine Identity`}>
|
||||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
<Tabs defaultValue={TabSections.General}>
|
<Tabs defaultValue={TabSections.General}>
|
||||||
<TabList>
|
<TabList>
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ export const CreateClientSecretModal = ({
|
|||||||
{!isLoading && data && data?.length === 0 && (
|
{!isLoading && data && data?.length === 0 && (
|
||||||
<Tr>
|
<Tr>
|
||||||
<Td colSpan={4}>
|
<Td colSpan={4}>
|
||||||
<EmptyState title="No client secrets have been created for this app client yet" icon={faKey} />
|
<EmptyState title="No client secrets have been created for this machine identity yet" icon={faKey} />
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const MachineIdentitySection = withPermission(
|
|||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<div className="flex justify-between mb-8">
|
<div className="flex justify-between mb-8">
|
||||||
<p className="text-xl font-semibold text-mineshaft-100">
|
<p className="text-xl font-semibold text-mineshaft-100">
|
||||||
App Clients
|
Machine Identities
|
||||||
</p>
|
</p>
|
||||||
<OrgPermissionCan
|
<OrgPermissionCan
|
||||||
I={OrgPermissionActions.Create}
|
I={OrgPermissionActions.Create}
|
||||||
@@ -65,7 +65,7 @@ export const MachineIdentitySection = withPermission(
|
|||||||
onClick={() => handlePopUpOpen("machineIdentity")}
|
onClick={() => handlePopUpOpen("machineIdentity")}
|
||||||
isDisabled={!isAllowed}
|
isDisabled={!isAllowed}
|
||||||
>
|
>
|
||||||
Create client
|
Create identity
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</OrgPermissionCan>
|
</OrgPermissionCan>
|
||||||
|
|||||||
@@ -151,29 +151,9 @@ export const MachineIdentityTable = ({
|
|||||||
customRole
|
customRole
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Tr className="h-10" key={`st-v3-${_id}`}>
|
<Tr className="h-10" key={`machine-identity-${_id}`}>
|
||||||
<Td>{name}</Td>
|
<Td>{name}</Td>
|
||||||
<Td>{clientId}</Td>
|
<Td>{clientId}</Td>
|
||||||
{/* <Td>
|
|
||||||
<OrgPermissionCan
|
|
||||||
I={OrgPermissionActions.Edit}
|
|
||||||
a={OrgPermissionSubjects.MachineIdentity}
|
|
||||||
>
|
|
||||||
{(isAllowed) => (
|
|
||||||
<Switch
|
|
||||||
id={`enable-service-token-${_id}`}
|
|
||||||
onCheckedChange={(value) => handleToggleStatus({
|
|
||||||
serviceTokenDataId: _id,
|
|
||||||
isActive: value
|
|
||||||
})}
|
|
||||||
isChecked={isActive}
|
|
||||||
isDisabled={!isAllowed}
|
|
||||||
>
|
|
||||||
<p className="w-12 mr-4">{isActive ? "Active" : "Inactive"}</p>
|
|
||||||
</Switch>
|
|
||||||
)}
|
|
||||||
</OrgPermissionCan>
|
|
||||||
</Td> */}
|
|
||||||
<Td>
|
<Td>
|
||||||
<OrgPermissionCan
|
<OrgPermissionCan
|
||||||
I={OrgPermissionActions.Edit}
|
I={OrgPermissionActions.Edit}
|
||||||
@@ -206,24 +186,8 @@ export const MachineIdentityTable = ({
|
|||||||
}}
|
}}
|
||||||
</OrgPermissionCan>
|
</OrgPermissionCan>
|
||||||
</Td>
|
</Td>
|
||||||
{/* <Td>
|
<Td>
|
||||||
{trustedIps.map(({
|
<div className="flex justify-end items-center">
|
||||||
_id: trustedIpId,
|
|
||||||
ipAddress,
|
|
||||||
prefix
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<p key={`service-token-${_id}-}-trusted-ip-${trustedIpId}`}>
|
|
||||||
{`${ipAddress}${prefix !== undefined ? `/${prefix}` : ""}`}
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Td> */}
|
|
||||||
{/* <Td>{accessTokenTTL}</Td> */}
|
|
||||||
{/* <Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td> */}
|
|
||||||
{/* <Td>{expiresAt ? format(new Date(expiresAt), "yyyy-MM-dd") : "-"}</Td> */}
|
|
||||||
|
|
||||||
<Td className="flex justify-end">
|
|
||||||
<Tooltip content="Manage client secrets">
|
<Tooltip content="Manage client secrets">
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
@@ -292,6 +256,7 @@ export const MachineIdentityTable = ({
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
</OrgPermissionCan>
|
</OrgPermissionCan>
|
||||||
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
);
|
);
|
||||||
@@ -299,7 +264,7 @@ export const MachineIdentityTable = ({
|
|||||||
{!isLoading && data && data?.length === 0 && (
|
{!isLoading && data && data?.length === 0 && (
|
||||||
<Tr>
|
<Tr>
|
||||||
<Td colSpan={7}>
|
<Td colSpan={7}>
|
||||||
<EmptyState title="No app clients have been created in this organization" icon={faServer} />
|
<EmptyState title="No machine identities have been created in this organization" icon={faServer} />
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const MembersPage = withProjectPermission(
|
|||||||
<Tab value={TabSections.Member}>People</Tab>
|
<Tab value={TabSections.Member}>People</Tab>
|
||||||
<Tab value={TabSections.MachineIdentities}>
|
<Tab value={TabSections.MachineIdentities}>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<p>App Clients</p>
|
<p>Machine Identities</p>
|
||||||
<div className="ml-2 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
<div className="ml-2 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||||
New
|
New
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ export const AddMachineIdentityModal = ({
|
|||||||
reset();
|
reset();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ModalContent title="Add App Client to Project">
|
<ModalContent title="Add Machine Identity to Project">
|
||||||
{filteredMachineMembershipOrgs.length ? (
|
{filteredMachineMembershipOrgs.length ? (
|
||||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
@@ -129,7 +129,7 @@ export const AddMachineIdentityModal = ({
|
|||||||
defaultValue={filteredMachineMembershipOrgs?.[0]?._id}
|
defaultValue={filteredMachineMembershipOrgs?.[0]?._id}
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="App Client"
|
label="Machine Identity"
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export const MachineIdentitySection = withProjectPermission(
|
|||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<div className="flex justify-between mb-8">
|
<div className="flex justify-between mb-8">
|
||||||
<p className="text-xl font-semibold text-mineshaft-100">
|
<p className="text-xl font-semibold text-mineshaft-100">
|
||||||
App Clients
|
Machine Identities
|
||||||
</p>
|
</p>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Create}
|
I={ProjectPermissionActions.Create}
|
||||||
@@ -76,7 +76,7 @@ export const MachineIdentitySection = withProjectPermission(
|
|||||||
onClick={() => handlePopUpOpen("machineIdentity")}
|
onClick={() => handlePopUpOpen("machineIdentity")}
|
||||||
isDisabled={!isAllowed}
|
isDisabled={!isAllowed}
|
||||||
>
|
>
|
||||||
Add client
|
Add identity
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ export const MachineIdentityTable = ({
|
|||||||
{!isLoading && data && data?.length === 0 && (
|
{!isLoading && data && data?.length === 0 && (
|
||||||
<Tr>
|
<Tr>
|
||||||
<Td colSpan={7}>
|
<Td colSpan={7}>
|
||||||
<EmptyState title="No app clients have been added to this project" icon={faServer} />
|
<EmptyState title="No machine identities have been added to this project" icon={faServer} />
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
)}
|
)}
|
||||||
|
|||||||