UNPKG

@aashari/mcp-server-aws-sso

Version:

Node.js/TypeScript MCP server for AWS Single Sign-On (SSO). Enables AI systems (LLMs) with tools to initiate SSO login (device auth flow), list accounts/roles, and securely execute AWS CLI commands using temporary credentials. Streamlines AI interaction w

159 lines (158 loc) 6.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.executeCommand = executeCommand; const logger_util_js_1 = require("../utils/logger.util.js"); // Import promisify and exec statically const node_util_1 = require("node:util"); const node_child_process_1 = require("node:child_process"); const vendor_aws_sso_accounts_service_js_1 = require("./vendor.aws.sso.accounts.service.js"); const logger = logger_util_js_1.Logger.forContext('services/vendor.aws.sso.exec.service.ts'); // Create promisified version once const exec = (0, node_util_1.promisify)(node_child_process_1.exec); /** * Execute AWS CLI command with temporary credentials * * Gets temporary credentials for the specified account and role, then executes * the AWS CLI command with those credentials in the environment. * * @param {string} accountId - AWS account ID to get credentials for * @param {string} roleName - AWS role name to assume via SSO * @param {string} commandString - AWS CLI command as a single string * @param {string} [region] - Optional AWS region override * @param {boolean} [forceRefreshCredentials] - Force refresh credentials even if cached * @returns {Promise<CommandExecutionResult>} Command execution result with stdout, stderr, and exit code * @throws {Error} If credentials cannot be obtained or command execution fails */ async function executeCommand(accountId, roleName, commandString, region, forceRefreshCredentials) { const methodLogger = logger.forMethod('executeCommand'); methodLogger.debug('Executing AWS CLI command', { accountId, roleName, command: commandString, region, forceRefresh: !!forceRefreshCredentials, }); // Validate parameters if (!accountId || !roleName) { throw new Error('Account ID and role name are required'); } if (!commandString) { throw new Error('Command is required'); } try { // Get credentials for the account and role const credentials = await (0, vendor_aws_sso_accounts_service_js_1.getAwsCredentials)({ accountId, roleName, region, forceRefresh: forceRefreshCredentials, }); methodLogger.debug('Obtained temporary credentials', { accountId, roleName, expiration: credentials.expiration.toISOString(), }); // Set up environment variables for the command const processEnv = { ...process.env }; // Add AWS credentials to the environment processEnv.AWS_ACCESS_KEY_ID = credentials.accessKeyId; processEnv.AWS_SECRET_ACCESS_KEY = credentials.secretAccessKey; processEnv.AWS_SESSION_TOKEN = credentials.sessionToken; // Fix PATH to prioritize working AWS CLI binary // Prepend /usr/local/aws-cli to PATH so the working binary is found first const currentPath = processEnv.PATH || ''; processEnv.PATH = `/usr/local/aws-cli:${currentPath}`; methodLogger.debug('Updated PATH for AWS CLI execution', { awsCliPath: '/usr/local/aws-cli', pathPrefix: processEnv.PATH.split(':').slice(0, 3), }); // Set region if provided if (region) { processEnv.AWS_REGION = region; processEnv.AWS_DEFAULT_REGION = region; } // Execute the command const result = await executeChildProcess(commandString, processEnv); methodLogger.debug('Command execution completed', { exitCode: result.exitCode, stdoutBytes: result.stdout.length, stderrBytes: result.stderr.length, }); // Check for credential validation errors if (result.exitCode !== 0 && !forceRefreshCredentials) { const credentialErrorPatterns = [ 'InvalidClientTokenId', 'security token.*invalid', 'InvalidToken', 'expired', 'credential.*verification', ]; const hasCredentialError = credentialErrorPatterns.some((pattern) => new RegExp(pattern, 'i').test(result.stderr)); if (hasCredentialError) { methodLogger.debug('Detected credential error, retrying with force refresh', { error: result.stderr.substring(0, 100), // Only log first 100 chars }); // Retry with forced credential refresh return executeCommand(accountId, roleName, commandString, region, true); } } return result; } catch (error) { methodLogger.error('Failed to execute command', error); throw error; } } /** * Execute child process with the given command and arguments * * Helper function to spawn a child process and collect its output. * * @param {string} commandString - Command string to execute via shell * @param {NodeJS.ProcessEnv} env - Environment variables for the process * @returns {Promise<CommandExecutionResult>} Command execution result */ async function executeChildProcess(commandString, env) { const methodLogger = logger.forMethod('executeChildProcess'); methodLogger.debug('Executing child process via shell', { command: commandString, }); try { // Execute using exec, which uses a shell const { stdout, stderr } = await exec(commandString, { env }); methodLogger.debug('Shell command completed successfully', { stdoutLength: stdout.length, stderrLength: stderr.length, }); return { stdout, stderr, exitCode: 0, // exec throws on non-zero exit code }; } catch (error) { // Handle errors from exec (includes non-zero exit codes) methodLogger.error('Shell command execution failed', { error }); let exitCode = 1; let stdout = ''; let stderr = ''; if (typeof error === 'object' && error !== null) { // Cast to check for common exec error properties const execError = error; exitCode = typeof execError.code === 'number' ? execError.code : 1; stdout = typeof execError.stdout === 'string' ? execError.stdout : ''; stderr = typeof execError.stderr === 'string' ? execError.stderr : ''; } else if (error instanceof Error) { // Fallback for generic Errors stderr = error.message; } return { stdout: String(stdout), stderr: String(stderr), exitCode: Number(exitCode) || 1, }; } }