import { fromEnv, fromIni, fromProcess, fromNodeProviderChain } from '@aws-sdk/credential-providers'; import { loadEnvConfig } from './env.js'; import { getLogger } from '../utils/logging.js'; import { ConfigurationError } from '../utils/error-handling.js'; import { IAMClient } from '@aws-sdk/client-iam'; import { AwsCredentialIdentityProvider } from '@aws-sdk/types'; const logger = getLogger(); /** * Types of credential providers available */ export enum CredentialProviderType { DEFAULT = 'default', ENV = 'environment', PROFILE = 'profile', PROCESS = 'process' } /** * Result of credential validation */ export interface CredentialValidationResult { valid: boolean; accountId?: string; userId?: string; arn?: string; message?: string; error?: Error; } /** * Create an AWS credential provider based on available credentials * @param providerType Type of credential provider to use * @returns AWS credential provider configuration */ export function createCredentialProvider(providerType: CredentialProviderType = CredentialProviderType.DEFAULT): AwsCredentialIdentityProvider { const config = loadEnvConfig(); try { switch (providerType) { case CredentialProviderType.ENV: // Use environment variables for credentials if (!config.AWS_ACCESS_KEY_ID || !config.AWS_SECRET_ACCESS_KEY) { throw new ConfigurationError( 'Missing required AWS credentials in environment variables', 'AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY' ); } return fromEnv(); case CredentialProviderType.PROFILE: // Use AWS profile for credentials if (!config.AWS_PROFILE) { throw new ConfigurationError( 'Missing required AWS profile name', 'AWS_PROFILE' ); } return fromIni({ profile: config.AWS_PROFILE }); case CredentialProviderType.PROCESS: // Use a credential process (defined in AWS config) return fromProcess(); case CredentialProviderType.DEFAULT: default: // Use the default provider chain return fromNodeProviderChain(); } } catch (error) { if (error instanceof ConfigurationError) { throw error; } throw new ConfigurationError( `Failed to create AWS credential provider: ${error instanceof Error ? error.message : String(error)}`, 'AWS_CREDENTIALS', error ); } } /** * Detect the best credential provider type based on available environment * @returns The most appropriate credential provider type */ export function detectCredentialProviderType(): CredentialProviderType { const config = loadEnvConfig(); // Check if explicit credentials are provided in environment if (config.AWS_ACCESS_KEY_ID && config.AWS_SECRET_ACCESS_KEY) { return CredentialProviderType.ENV; } // Check if a profile is specified if (config.AWS_PROFILE) { return CredentialProviderType.PROFILE; } // Fall back to default provider chain return CredentialProviderType.DEFAULT; } /** * Validate AWS credentials by making a test API call * @param region AWS region to use * @returns Validation result with caller identity information */ export async function validateCredentials(region: string = 'us-east-1'): Promise { try { const providerType = detectCredentialProviderType(); logger.debug(`Using credential provider type: ${providerType}`); const credentials = createCredentialProvider(providerType); // eslint-disable-next-line @typescript-eslint/no-unused-vars const iamClient = new IAMClient({ region, credentials }); // For simplicity, we'll just check if we can get credentials // This avoids IAM permissions issues if GetCallerIdentity isn't available logger.debug('Validating AWS credentials'); // eslint-disable-next-line @typescript-eslint/no-unused-vars const credResult = await credentials(); return { valid: true, accountId: 'validated', message: 'AWS credentials are valid' }; } catch (error) { logger.error('AWS credential validation failed', { error }); let message = 'Invalid or missing AWS credentials'; if (error instanceof Error) { // Enhance the error message based on common AWS credential issues if (error.message.includes('could not find credentials')) { message = 'AWS credentials not found. Please set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables, or configure the AWS CLI.'; } else if (error.message.includes('expired')) { message = 'AWS credentials have expired. Please refresh your credentials.'; } else if (error.message.includes('not authorized')) { message = 'AWS credentials are not authorized to perform this action. Check IAM permissions.'; } } return { valid: false, message, error: error instanceof Error ? error : new Error(String(error)) }; } } /** * Check if AWS credentials have sufficient permissions for required operations * This can be extended to check specific permissions as needed * @param region AWS region to use * @returns Promise resolving to validation result */ export async function checkCredentialPermissions(region: string = 'us-east-1'): Promise { // Start with basic validation const validationResult = await validateCredentials(region); if (!validationResult.valid) { return validationResult; } // TODO: Add specific permission checks for CloudWatch and CloudTrail // For example, checking ListLogGroups permission for CloudWatch Logs // and LookupEvents permission for CloudTrail logger.info('AWS credential validation successful', { accountId: validationResult.accountId }); return validationResult; }