mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Bring back ssh host read permission
This commit is contained in:
@@ -412,7 +412,7 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
||||
sshHostId: z.string().trim().describe(SSH_HOSTS.GET_USER_CA_PUBLIC_KEY.sshHostId)
|
||||
}),
|
||||
response: {
|
||||
200: z.string()
|
||||
200: z.string().describe(SSH_HOSTS.GET_USER_CA_PUBLIC_KEY.publicKey)
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
@@ -433,7 +433,7 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
||||
sshHostId: z.string().trim().describe(SSH_HOSTS.GET_HOST_CA_PUBLIC_KEY.sshHostId)
|
||||
}),
|
||||
response: {
|
||||
200: z.string()
|
||||
200: z.string().describe(SSH_HOSTS.GET_HOST_CA_PUBLIC_KEY.publicKey)
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
|
||||
@@ -68,6 +68,7 @@ export enum ProjectPermissionGroupActions {
|
||||
}
|
||||
|
||||
export enum ProjectPermissionSshHostActions {
|
||||
Read = "read",
|
||||
Create = "create",
|
||||
Edit = "edit",
|
||||
Delete = "delete",
|
||||
@@ -657,6 +658,7 @@ const buildAdminPermissionRules = () => {
|
||||
can(
|
||||
[
|
||||
ProjectPermissionSshHostActions.Edit,
|
||||
ProjectPermissionSshHostActions.Read,
|
||||
ProjectPermissionSshHostActions.Create,
|
||||
ProjectPermissionSshHostActions.Delete,
|
||||
ProjectPermissionSshHostActions.IssueHostCert
|
||||
@@ -924,6 +926,8 @@ const buildMemberPermissionRules = () => {
|
||||
can([ProjectPermissionActions.Create], ProjectPermissionSub.SshCertificates);
|
||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificateTemplates);
|
||||
|
||||
can([ProjectPermissionSshHostActions.Read], ProjectPermissionSub.SshHosts);
|
||||
|
||||
can(
|
||||
[
|
||||
ProjectPermissionCmekActions.Create,
|
||||
|
||||
@@ -415,7 +415,7 @@ export const sshHostServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
await permissionService.getProjectPermission({
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId: host.projectId,
|
||||
@@ -424,6 +424,13 @@ export const sshHostServiceFactory = ({
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSshHostActions.Read,
|
||||
subject(ProjectPermissionSub.SshHosts, {
|
||||
hostname: host.hostname
|
||||
})
|
||||
);
|
||||
|
||||
return host;
|
||||
};
|
||||
|
||||
|
||||
@@ -430,7 +430,8 @@ export const validateExternalSshCaKeyPair = async (publicKey: string, privateKey
|
||||
|
||||
if (publicKey.trim() !== derivedPublicKey.trim()) {
|
||||
throw new BadRequestError({
|
||||
message: "Failed to validate matching SSH key pair."
|
||||
message:
|
||||
"Failed to validate matching SSH key pair: The provided public key does not match the public key derived from the private key."
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1381,10 +1381,12 @@ export const SSH_HOSTS = {
|
||||
signedKey: "The SSH certificate or signed SSH public key."
|
||||
},
|
||||
GET_USER_CA_PUBLIC_KEY: {
|
||||
sshHostId: "The ID of the SSH host to get the user SSH CA public key for."
|
||||
sshHostId: "The ID of the SSH host to get the user SSH CA public key for.",
|
||||
publicKey: "The public key of the user SSH CA linked to the SSH host."
|
||||
},
|
||||
GET_HOST_CA_PUBLIC_KEY: {
|
||||
sshHostId: "The ID of the SSH host to get the host SSH CA public key for."
|
||||
sshHostId: "The ID of the SSH host to get the host SSH CA public key for.",
|
||||
publicKey: "The public key of the host SSH CA linked to the SSH host."
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
|
||||
import {
|
||||
@@ -15,6 +15,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionSshHostActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
import { TProjectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-service";
|
||||
@@ -1077,7 +1078,7 @@ export const projectServiceFactory = ({
|
||||
actor,
|
||||
projectId
|
||||
}: TListProjectSshHostsDTO) => {
|
||||
await permissionService.getProjectPermission({
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
@@ -1086,8 +1087,27 @@ export const projectServiceFactory = ({
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
const allowedHosts = [];
|
||||
|
||||
// (dangtony98): room to optimize
|
||||
const hosts = await sshHostDAL.findSshHostsWithLoginMappings(projectId);
|
||||
return hosts;
|
||||
|
||||
for (const host of hosts) {
|
||||
try {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSshHostActions.Read,
|
||||
subject(ProjectPermissionSub.SshHosts, {
|
||||
hostname: host.hostname
|
||||
})
|
||||
);
|
||||
|
||||
allowedHosts.push(host);
|
||||
} catch {
|
||||
// intentionally ignore projects where user lacks access
|
||||
}
|
||||
}
|
||||
|
||||
return allowedHosts;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,7 @@ unauthorized access, and SSH key sprawl.
|
||||
The following concepts are useful to know when working with Infisical SSH:
|
||||
|
||||
- SSH Certificate Authority (CA): A trusted authority that issues SSH certificates.
|
||||
- Certificate Template: A set of policies bound to a SSH CA for certificates issued under that template; a CA can possess multiple templates, each with different policies for a different purpose (e.g. for admin versus developer access).
|
||||
- Certificate Template: A set of policies bound to an SSH CA for certificates issued under that template; a CA can possess multiple templates, each with different policies for a different purpose (e.g. for admin versus developer access).
|
||||
- SSH Certificate: A short-lived, credential issued by the SSH CA granting time-bound access to infrastructure.
|
||||
|
||||
<div align="center">
|
||||
@@ -30,10 +30,10 @@ graph TD
|
||||
|
||||
</div>
|
||||
|
||||
When using Infisical SSH to provision client access to a remote host, an operator must create a SSH CA in Infisical; a certificate template under it,
|
||||
When using Infisical SSH to provision client access to a remote host, an operator must create an SSH CA in Infisical; a certificate template under it,
|
||||
specifying policies such as allowed users that can be requested under that template by a client; and configure the host to trust certificates issued by the Infisical SSH CA.
|
||||
|
||||
When a client needs access to a host, they authenticate with Infisical and request a SSH certificate (and optionally key pair)
|
||||
When a client needs access to a host, they authenticate with Infisical and request an SSH certificate (and optionally key pair)
|
||||
to be used to access the host for a time-bound session as part of the SSH operation.
|
||||
|
||||
## Client Workflow
|
||||
@@ -68,7 +68,7 @@ At a high-level, Infisical issues a signed SSH certificate to a client that can
|
||||
To be more specific:
|
||||
|
||||
1. The client authenticates with Infisical; this can be done using a user or machine identity [authentication method](/documentation/platform/identities/machine-identities) or a user [authentication method](/documentation/platform/identities/user-identities).
|
||||
2. The client makes an authenticated request for an SSH certificate via either the `/api/v1/ssh/issue` or `/api/v1/ssh/sign` endpoints. Note that if the client wishes to use an existing SSH key pair, it can use the `/api/v1/ssh/sign` endpoint; otherwise, it can use the `/api/v1/ssh/issue` endpoint to have Infisical issue a new SSH key pair in conjunction with the certificate.
|
||||
2. The client makes an authenticated request for an SSH certificate via either the `/api/v1/ssh/issue` or `/api/v1/ssh/sign` endpoints. Note that if the client wishes to use an existing SSH key pair, it can use the `/api/v1/ssh/sign` endpoint; otherwise, it can use the `/api/v1/ssh/issue` endpoint to have Infisical issue a new SSH key pair along with the certificate.
|
||||
3. The client uses the issued SSH certificate (and potentially SSH key pair) to temporarily access the host.
|
||||
|
||||
<Note>
|
||||
@@ -83,12 +83,12 @@ In the following steps, we explore how to configure Infisical SSH to start issui
|
||||
as part of the SSH operation.
|
||||
|
||||
<Steps>
|
||||
<Step title="Configuring a SSH CA for client key signing">
|
||||
1.1. Start by creating a SSH project in the SSH tab of your organization.
|
||||
<Step title="Configuring an SSH CA for client key signing">
|
||||
1.1. Start by creating an SSH project in the SSH tab of your organization.
|
||||
|
||||

|
||||
|
||||
1.2. Next, create a SSH CA in the **Certificate Authorities** tab of the
|
||||
1.2. Next, create an SSH CA in the **Certificate Authorities** tab of the
|
||||
project; this CA will be used for client key signing.
|
||||
|
||||

|
||||
@@ -125,7 +125,7 @@ as part of the SSH operation.
|
||||
- Allow Host Certificates: Whether or not to allow issuance of host certificates; this is not relevant for this step.
|
||||
- Allow Custom Key IDs: Whether or not to allow clients to specify a custom key ID to be included on the certificate as part of the certificate request.
|
||||
|
||||
2.2. Finally, add the user(s) you wish to be able to request a SSH certificate to the SSH project through the **Access Control** tab.
|
||||
2.2. Finally, add the user(s) you wish to be able to request an SSH certificate to the SSH project through the **Access Control** tab.
|
||||
|
||||
</Step>
|
||||
<Step title="Configuring the remote host to trust the client">
|
||||
@@ -185,7 +185,7 @@ infisical login
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Obtain a SSH certificate for the client and load it into the SSH agent">
|
||||
<Step title="Obtain an SSH certificate for the client and load it into the SSH agent">
|
||||
Run the `infisical ssh issue-credentials` command, specifying the `--addToAgent` flag to automatically load the SSH certificate into the SSH agent.
|
||||
```bash
|
||||
infisical ssh issue-credentials --certificateTemplateId=<certificate-template-id> --principals=<username> --addToAgent
|
||||
@@ -229,7 +229,7 @@ If the remote host does not have an existing SSH key pair, you can generate a ne
|
||||
</Note>
|
||||
|
||||
<Steps>
|
||||
<Step title="Configuring a SSH CA for host key signing">
|
||||
<Step title="Configuring an SSH CA for host key signing">
|
||||
1.1. In the same SSH project, create another SSH CA in the **Certificate Authorities** tab; this CA will be used for host key signing.
|
||||
|
||||

|
||||
@@ -266,7 +266,7 @@ If the remote host does not have an existing SSH key pair, you can generate a ne
|
||||
</Step>
|
||||
<Step title="Configuring the remote host with an SSH certificate">
|
||||
|
||||
2.1. Obtain a SSH certificate for the host by requesting one from the **Certificates** tab.
|
||||
3.1. Obtain an SSH certificate for the host by requesting one from the **Certificates** tab.
|
||||
|
||||

|
||||
|
||||
@@ -280,15 +280,15 @@ If the remote host does not have an existing SSH key pair, you can generate a ne
|
||||
|
||||

|
||||
|
||||
2.2. Create a file containing the certificate in the SSH folder of the remote host; we'll call it `ssh_host_key-cert.pub`.
|
||||
3.2. Create a file containing the certificate in the SSH folder of the remote host; we'll call it `ssh_host_key-cert.pub`.
|
||||
|
||||
2.3. Set permissions on the certificate to be `0640`:
|
||||
3.3. Set permissions on the certificate to be `0640`:
|
||||
|
||||
```bash
|
||||
sudo chmod 0640 /etc/ssh/ssh_host_key-cert.pub
|
||||
```
|
||||
|
||||
2.4. Next, add the following lines to the `/etc/ssh/sshd_config` file on the remote host.
|
||||
3.4. Next, add the following lines to the `/etc/ssh/sshd_config` file on the remote host.
|
||||
|
||||
```bash
|
||||
HostKey /etc/ssh/ssh_host_rsa_key
|
||||
@@ -299,7 +299,7 @@ If the remote host does not have an existing SSH key pair, you can generate a ne
|
||||
You should adjust the `HostKey` directive to match the path to the host's SSH private key as used in step 1.
|
||||
</Note>
|
||||
|
||||
2.5. Finally, reload the SSH daemon on the remote host to apply the changes.
|
||||
3.5. Finally, reload the SSH daemon on the remote host to apply the changes.
|
||||
|
||||
```bash
|
||||
sudo systemctl reload sshd
|
||||
@@ -307,7 +307,7 @@ If the remote host does not have an existing SSH key pair, you can generate a ne
|
||||
|
||||
</Step>
|
||||
<Step title="Configuring the client to trust the remote host">
|
||||
3.1. Begin by downloading the host CA's public key from the CA's details section.
|
||||
4.1. Begin by downloading the host CA's public key from the CA's details section.
|
||||
|
||||

|
||||
|
||||
@@ -315,7 +315,7 @@ If the remote host does not have an existing SSH key pair, you can generate a ne
|
||||
The CA's public key can also be retrieved programmatically via API by making a `GET` request to the endpoint [here](/api-reference/endpoints/ssh/ca/public-key).
|
||||
</Note>
|
||||
|
||||
3.2. Next, add the resulting public key to the `known_hosts` file on the client machine (e.g. at the path `~/.ssh/known_hosts`).
|
||||
4.2. Next, add the resulting public key to the `known_hosts` file on the client machine (e.g. at the path `~/.ssh/known_hosts`).
|
||||
|
||||
```bash
|
||||
@cert-authority *.example.com ssh-rsa ...
|
||||
|
||||
@@ -6,8 +6,8 @@ description: "Learn how to securely provision user SSH access to your infrastruc
|
||||
|
||||
## Concept
|
||||
|
||||
Infisical SSH can be used to provide users short-lived, secure SSH access to infrastructure; the underlying technology is powered by SSH certificates
|
||||
and improves on the limitations of traditional SSH key-based authentication via mitigation of private key compromise, static key management,
|
||||
Infisical SSH can be configured to provide users on your team short-lived, secure SSH access to infrastructure. Under the hood, it uses SSH certificates
|
||||
and improves upon traditional SSH key-based authentication by mitigating private key compromise, static key management,
|
||||
unauthorized access, and SSH key sprawl.
|
||||
|
||||
The following entities and concepts are important to understand when using Infisical SSH:
|
||||
@@ -90,6 +90,17 @@ we will register a remote host with Infisical through a [machine identity](/docu
|
||||
📄 Updated sshd_config entries
|
||||
```
|
||||
|
||||
Finally, use the following command to reload the SSH daemon on the remote host to apply the changes:
|
||||
|
||||
```bash
|
||||
sudo systemctl reload sshd
|
||||
```
|
||||
|
||||
<Note>
|
||||
The command may differ depending on the host. For older versions of Ubuntu/Debian/CentOS, you may need to use `sudo service ssh reload` instead;
|
||||
for Alpine or minimal systems, `/etc/init.d/sshd reload`.
|
||||
</Note>
|
||||
|
||||
Back in Infisical, you should now see the remote host you just registered in the Infisical SSH project you created in step 1 under the **Hosts** tab.
|
||||
|
||||

|
||||
|
||||
@@ -76,6 +76,7 @@ export enum ProjectPermissionGroupActions {
|
||||
}
|
||||
|
||||
export enum ProjectPermissionSshHostActions {
|
||||
Read = "read",
|
||||
Create = "create",
|
||||
Edit = "edit",
|
||||
Delete = "delete",
|
||||
|
||||
@@ -110,6 +110,7 @@ const GroupPolicyActionSchema = z.object({
|
||||
});
|
||||
|
||||
const SshHostPolicyActionSchema = z.object({
|
||||
[ProjectPermissionSshHostActions.Read]: z.boolean().optional(),
|
||||
[ProjectPermissionSshHostActions.Create]: z.boolean().optional(),
|
||||
[ProjectPermissionSshHostActions.Edit]: z.boolean().optional(),
|
||||
[ProjectPermissionSshHostActions.Delete]: z.boolean().optional(),
|
||||
@@ -610,6 +611,9 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
||||
[ProjectPermissionSshHostActions.Create]: action.includes(
|
||||
ProjectPermissionSshHostActions.Create
|
||||
),
|
||||
[ProjectPermissionSshHostActions.Read]: action.includes(
|
||||
ProjectPermissionSshHostActions.Read
|
||||
),
|
||||
[ProjectPermissionSshHostActions.IssueHostCert]: action.includes(
|
||||
ProjectPermissionSshHostActions.IssueHostCert
|
||||
),
|
||||
@@ -945,6 +949,7 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
||||
[ProjectPermissionSub.SshHosts]: {
|
||||
title: "SSH Hosts",
|
||||
actions: [
|
||||
{ label: "Read", value: ProjectPermissionSshHostActions.Read },
|
||||
{ label: "Create", value: ProjectPermissionSshHostActions.Create },
|
||||
{ label: "Modify", value: ProjectPermissionSshHostActions.Edit },
|
||||
{ label: "Remove", value: ProjectPermissionSshHostActions.Delete },
|
||||
|
||||
@@ -42,7 +42,7 @@ const schema = z
|
||||
(val) => ms(val) > 0,
|
||||
"TTL must be a valid time string such as 2 days, 1d, 2h 1y, ..."
|
||||
)
|
||||
.default("8h"),
|
||||
.default("8h, 1d, 30m"),
|
||||
loginMappings: z
|
||||
.object({
|
||||
loginUser: z.string().trim().min(1),
|
||||
@@ -206,7 +206,7 @@ export const SshHostModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
errorText={error?.message}
|
||||
isRequired
|
||||
>
|
||||
<Input {...field} placeholder="8h" />
|
||||
<Input {...field} placeholder="8h, 1d, 30m" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
@@ -328,7 +328,7 @@ export const SshHostModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
{(value.length === 0 ? [""] : value).map(
|
||||
(principal: string, principalIndex: number) => (
|
||||
<div
|
||||
key={`${metadataFieldId}-principal-${principal || principalIndex}`}
|
||||
key={`${metadataFieldId}-principal-${principal}`}
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<div className="flex-1">
|
||||
|
||||
Reference in New Issue
Block a user