Skip to content

Commit

Permalink
Azure SSH: provide user friendly error messages (#152)
Browse files Browse the repository at this point in the history
This PR applies a small refactor that improves the error handling of
during auth steps for the Azure SSH plugin. When a user cancels their
login attempt or if we fail to set the users active Azure subscription.
The refactor introduces a knew `KnownError` type and a utility method
`normalizeAzureCliError` that should make it easy to extend and capture
new errors as we discover them.
  • Loading branch information
GGonryun authored Dec 6, 2024
1 parent bc2a0c4 commit a9c8f91
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 25 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@p0security/cli",
"version": "0.13.1",
"version": "0.13.2",
"description": "Execute infra CLI commands with P0 grants",
"main": "index.ts",
"repository": {
Expand Down
98 changes: 74 additions & 24 deletions src/plugins/azure/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,31 @@ You should have received a copy of the GNU General Public License along with @p0
**/
import { print2 } from "../../drivers/stdio";
import { exec } from "../../util";
import { KnownError } from "./types";

const knownLoginErrors: KnownError[] = [
{
pattern:
/WARNING: A web browser has been opened at .+ Please continue the login in the web browser.+/,
message: "Login attempt was cancelled. Please try again.",
},
];

const knownAccountSetErrors: KnownError[] = [
{
pattern: /ERROR: The subscription of '.+' doesn't exist in cloud '.+'.+/,
message: "Failed to set the active Azure subscription. Please try again.",
},
];

const normalizeAzureCliError = (error: any, normalizedErrors: KnownError[]) => {
for (const { pattern, message } of normalizedErrors) {
if (pattern.test(error.stderr)) {
throw message;
}
}
throw error;
};

export const azLoginCommand = () => ({
command: "az",
Expand All @@ -26,16 +51,7 @@ export const azAccountSetCommand = (subscriptionId: string) => ({
args: ["account", "set", "--subscription", subscriptionId],
});

export const azLogin = async (
subscriptionId: string,
options: { debug?: boolean } = {}
) => {
const { debug } = options;

if (debug) print2("Logging in to Azure...");

// Logging out first ensures that any cached credentials are cleared.
// https://github.com/Azure/azure-cli/issues/29161
const performLogout = async ({ debug }: { debug?: boolean }) => {
try {
const { command: azLogoutExe, args: azLogoutArgs } = azLogoutCommand();
const logoutResult = await exec(azLogoutExe, azLogoutArgs, { check: true });
Expand All @@ -50,24 +66,58 @@ export const azLogin = async (
print2(`Skipping logout: ${error.stderr}`);
}
}
};

const { command: azLoginExe, args: azLoginArgs } = azLoginCommand();
const loginResult = await exec(azLoginExe, azLoginArgs, { check: true });
const performLogin = async (
subscriptionId: string,
{ debug }: { debug?: boolean }
) => {
try {
const { command: azLoginExe, args: azLoginArgs } = azLoginCommand();
const loginResult = await exec(azLoginExe, azLoginArgs, { check: true });

if (debug) {
print2(loginResult.stdout);
print2(loginResult.stderr);
print2(`Setting active Azure subscription to ${subscriptionId}...`);
if (debug) {
print2(loginResult.stdout);
print2(loginResult.stderr);
print2(`Setting active Azure subscription to ${subscriptionId}...`);
}
} catch (error: any) {
throw normalizeAzureCliError(error, knownLoginErrors);
}
};

const { command: azAccountSetExe, args: azAccountSetArgs } =
azAccountSetCommand(subscriptionId);
const accountSetResult = await exec(azAccountSetExe, azAccountSetArgs, {
check: true,
});
const performSetAccount = async (
subscriptionId: string,
{ debug }: { debug?: boolean }
) => {
try {
const { command: azAccountSetExe, args: azAccountSetArgs } =
azAccountSetCommand(subscriptionId);
const accountSetResult = await exec(azAccountSetExe, azAccountSetArgs, {
check: true,
});

if (debug) {
print2(accountSetResult.stdout);
print2(accountSetResult.stderr);
if (debug) {
print2(accountSetResult.stdout);
print2(accountSetResult.stderr);
}
} catch (error) {
throw normalizeAzureCliError(error, knownAccountSetErrors);
}
};

export const azLogin = async (
subscriptionId: string,
options: { debug?: boolean } = {}
) => {
const { debug } = options;
if (debug) print2("Logging in to Azure...");

// Logging out first ensures that any cached credentials are cleared.
// https://github.com/Azure/azure-cli/issues/29161
await performLogout(options);

await performLogin(subscriptionId, options);

await performSetAccount(subscriptionId, options);
};
5 changes: 5 additions & 0 deletions src/plugins/azure/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import { PermissionSpec } from "../../types/request";
import { CliPermissionSpec } from "../../types/ssh";
import { CommonSshPermissionSpec } from "../ssh/types";

export type KnownError = {
pattern: RegExp;
message: string;
};

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

export type AzureSsh = CliPermissionSpec<
Expand Down

0 comments on commit a9c8f91

Please sign in to comment.