Skip to content

Commit

Permalink
Initial Azure SSH work; login and installer (#139)
Browse files Browse the repository at this point in the history
This PR represents the start of work on Azure SSH in the P0 CLI. It adds
a basic shell of an `azureSshProvider` that is the bare minimum needed
to start development work, and implements automated installers for both
Homebrew (required for installing the Azure CLI on macOS) and the Azure
CLI (`az`) itself, and a simple call to `az login` to log in to Azure
when `p0 ssh --provider azure` is invoked.
  • Loading branch information
p0-andrewa authored Nov 13, 2024
1 parent 0003933 commit e9b2244
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 4 deletions.
2 changes: 2 additions & 0 deletions src/commands/shared/ssh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { createKeyPair } from "../../common/keys";
import { doc } from "../../drivers/firestore";
import { print2 } from "../../drivers/stdio";
import { awsSshProvider } from "../../plugins/aws/ssh";
import { azureSshProvider } from "../../plugins/azure/ssh";
import { gcpSshProvider } from "../../plugins/google/ssh";
import { SshConfig } from "../../plugins/ssh/types";
import { Authn } from "../../types/identity";
Expand Down Expand Up @@ -58,6 +59,7 @@ export const SSH_PROVIDERS: Record<
SshProvider<any, any, any, any>
> = {
aws: awsSshProvider,
azure: azureSshProvider,
gcloud: gcpSshProvider,
};

Expand Down
15 changes: 15 additions & 0 deletions src/common/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export type SupportedPlatform = (typeof SupportedPlatforms)[number];
export const AwsItems = ["aws"] as const;
export type AwsItem = (typeof AwsItems)[number];

export const HomebrewItems = ["brew"] as const;
export type HomebrewItem = (typeof HomebrewItems)[number];

export type InstallMetadata = {
label: string;
commands: Record<SupportedPlatform, Readonly<string[]>>;
Expand All @@ -40,6 +43,18 @@ export const AwsInstall: Readonly<Record<AwsItem, InstallMetadata>> = {
},
};

export const HomebrewInstall: Readonly<Record<HomebrewItem, InstallMetadata>> =
{
brew: {
label: "Homebrew",
commands: {
darwin: [
'/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
],
},
},
};

const printToInstall = <
T extends string,
U extends Readonly<Record<T, InstallMetadata>>,
Expand Down
32 changes: 32 additions & 0 deletions src/plugins/azure/install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/** Copyright © 2024-present P0 Security
This file is part of @p0security/cli
@p0security/cli is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License.
@p0security/cli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see <https://www.gnu.org/licenses/>.
**/
import {
ensureInstall,
HomebrewInstall,
HomebrewItems,
InstallMetadata,
} from "../../common/install";

const AzItems = [...HomebrewItems, "az"] as const;
type AzItem = (typeof AzItems)[number];

const AzInstall: Readonly<Record<AzItem, InstallMetadata>> = {
...HomebrewInstall,
az: {
label: "Azure command-line interface",
commands: {
darwin: ["brew update", "brew install azure-cli"],
},
},
};

export const ensureAzInstall = async () =>
await ensureInstall(AzItems, AzInstall);
78 changes: 78 additions & 0 deletions src/plugins/azure/ssh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/** Copyright © 2024-present P0 Security
This file is part of @p0security/cli
@p0security/cli is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License.
@p0security/cli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see <https://www.gnu.org/licenses/>.
**/
import { SshProvider } from "../../types/ssh";
import { exec } from "../../util";
import { importSshKey } from "../google/ssh-key";
import { ensureAzInstall } from "./install";
import { AzureSshPermissionSpec, AzureSshRequest } from "./types";

// TODO: Determine what this value should be for Azure
const PROPAGATION_TIMEOUT_LIMIT_MS = 2 * 60 * 1000;

export const azureSshProvider: SshProvider<
AzureSshPermissionSpec,
{ linuxUserName: string },
AzureSshRequest
> = {
// TODO: Natively support Azure login in P0 CLI
cloudProviderLogin: async () => {
// Always invoke `az login` before each SSH access. This is needed because
// Azure permissions are only updated upon login.
await exec("az", ["login"]);
return undefined;
},

ensureInstall: async () => {
if (!(await ensureAzInstall())) {
throw "Please try again after installing the Azure CLI tool.";
}
},

friendlyName: "Microsoft Azure",

loginRequiredMessage: "Please log in to Azure with 'az login' to continue.",

// TODO: Determine value
loginRequiredPattern: undefined,

propagationTimeoutMs: PROPAGATION_TIMEOUT_LIMIT_MS,

// TODO: Implement
preTestAccessPropagationArgs: () => undefined,

// TODO: Determine if necessary
proxyCommand: () => [],

// TODO: Determine if necessary
reproCommands: () => undefined,

// TODO: Placeholder
requestToSsh: (request) => ({
type: "azure",
id: request.permission.spec.instanceId,
instanceId: request.permission.spec.instanceId,
linuxUserName: request.cliLocalData.linuxUserName,
}),

// TODO: Implement
unprovisionedAccessPatterns: [],

// TODO: Placeholder
toCliRequest: async (request, options) => ({
...request,
cliLocalData: {
linuxUserName: await importSshKey(
request.permission.spec.publicKey,
options
),
},
}),
};
39 changes: 39 additions & 0 deletions src/plugins/azure/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/** Copyright © 2024-present P0 Security
This file is part of @p0security/cli
@p0security/cli is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License.
@p0security/cli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with @p0security/cli. If not, see <https://www.gnu.org/licenses/>.
**/
import { PermissionSpec } from "../../types/request";
import { CliPermissionSpec } from "../../types/ssh";
import { CommonSshPermissionSpec } from "../ssh/types";

export type AzureSshPermissionSpec = PermissionSpec<"ssh", AzureSshPermission>;

// TODO: Placeholder; confirm this is correct
export type AzureSsh = CliPermissionSpec<
AzureSshPermissionSpec,
{ linuxUserName: string }
>;

export type AzureSshPermission = {
type: "session";
spec: CommonSshPermissionSpec & AzureNodeSpec;
};

// TODO: Placeholder; probably wrong
export type AzureNodeSpec = {
type: "azure";
instanceId: string;
sudo?: boolean;
};

// TODO: Placeholder; probably wrong
export type AzureSshRequest = AzureNodeSpec & {
id: string;
linuxUserName: string;
};
16 changes: 12 additions & 4 deletions src/types/ssh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import {
AwsSshPermissionSpec,
AwsSshRequest,
} from "../plugins/aws/types";
import {
AzureSsh,
AzureSshPermissionSpec,
AzureSshRequest,
} from "../plugins/azure/types";
import {
GcpSsh,
GcpSshPermissionSpec,
Expand All @@ -22,8 +27,11 @@ import {
import { Authn } from "./identity";
import { Request } from "./request";

export type CliSshRequest = AwsSsh | GcpSsh;
export type PluginSshRequest = AwsSshPermissionSpec | GcpSshPermissionSpec;
export type CliSshRequest = AwsSsh | AzureSsh | GcpSsh;
export type PluginSshRequest =
| AwsSshPermissionSpec
| AzureSshPermissionSpec
| GcpSshPermissionSpec;

export type CliPermissionSpec<
P extends PluginSshRequest,
Expand All @@ -33,7 +41,7 @@ export type CliPermissionSpec<
};

// The prefix of installed SSH accounts in P0 is the provider name
export const SupportedSshProviders = ["aws", "gcloud"] as const;
export const SupportedSshProviders = ["aws", "azure", "gcloud"] as const;
export type SupportedSshProvider = (typeof SupportedSshProviders)[number];

export type SshProvider<
Expand Down Expand Up @@ -96,4 +104,4 @@ export type SshProvider<
) => Promise<Request<CliSshRequest>>;
};

export type SshRequest = AwsSshRequest | GcpSshRequest;
export type SshRequest = AwsSshRequest | AzureSshRequest | GcpSshRequest;

0 comments on commit e9b2244

Please sign in to comment.