153 lines
5.9 KiB
TypeScript
153 lines
5.9 KiB
TypeScript
import { S3Client } from "@aws-sdk/client-s3";
|
|
import { LambdaClient } from "@aws-sdk/client-lambda";
|
|
import { EKSClient } from "@aws-sdk/client-eks";
|
|
import { EC2Client } from "@aws-sdk/client-ec2";
|
|
import { RDSClient } from "@aws-sdk/client-rds";
|
|
import { RDSDataClient } from "@aws-sdk/client-rds-data";
|
|
import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
|
|
import { CloudWatchLogsClient } from "@aws-sdk/client-cloudwatch-logs";
|
|
import { SSMClient } from "@aws-sdk/client-ssm";
|
|
import { KMSClient } from "@aws-sdk/client-kms";
|
|
import { SFNClient } from "@aws-sdk/client-sfn";
|
|
import { ElasticLoadBalancingV2Client } from "@aws-sdk/client-elastic-load-balancing-v2";
|
|
import { EventBridgeClient } from "@aws-sdk/client-eventbridge";
|
|
import { SQSClient } from "@aws-sdk/client-sqs";
|
|
import { IAMClient } from "@aws-sdk/client-iam";
|
|
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
import { APIGatewayClient } from "@aws-sdk/client-api-gateway";
|
|
import { CloudFormationClient } from "@aws-sdk/client-cloudformation";
|
|
import { AppConfigClient } from "@aws-sdk/client-appconfig";
|
|
import { SNSClient } from "@aws-sdk/client-sns";
|
|
import { ECRClient } from "@aws-sdk/client-ecr";
|
|
import { CodeBuildClient } from "@aws-sdk/client-codebuild";
|
|
import { CodePipelineClient } from "@aws-sdk/client-codepipeline";
|
|
import { CodeDeployClient } from "@aws-sdk/client-codedeploy";
|
|
|
|
const endpoint = process.env.FLOCI_ENDPOINT;
|
|
const region = process.env.AWS_REGION || "us-east-1";
|
|
|
|
// Floci derives the AWS account from AWS_ACCESS_KEY_ID: a value that is exactly
|
|
// 12 digits is used verbatim as the account id, and resources are isolated per
|
|
// account. Any other key (e.g. "test", "AKIA...") falls back to
|
|
// FLOCI_DEFAULT_ACCOUNT_ID (000000000000). We lean on that contract to scope the
|
|
// console to an account: the access key *is* the account id and the secret can
|
|
// be any non-empty string (Floci does not validate it).
|
|
const DEFAULT_ACCOUNT_ID = process.env.FLOCI_DEFAULT_ACCOUNT_ID || "000000000000";
|
|
const SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY || "floci";
|
|
|
|
export function isAccountId(value: string | null | undefined): value is string {
|
|
return typeof value === "string" && /^\d{12}$/.test(value);
|
|
}
|
|
|
|
/** The account used when a request does not (validly) specify one. */
|
|
export function defaultAccountId(): string {
|
|
const envKey = process.env.AWS_ACCESS_KEY_ID;
|
|
return isAccountId(envKey) ? envKey : DEFAULT_ACCOUNT_ID;
|
|
}
|
|
|
|
/** Normalize a caller-supplied account id, falling back to the default. */
|
|
export function resolveAccountId(raw?: string | null): string {
|
|
return isAccountId(raw) ? raw : defaultAccountId();
|
|
}
|
|
|
|
export type AwsClients = {
|
|
s3: S3Client;
|
|
lambda: LambdaClient;
|
|
eks: EKSClient;
|
|
ec2: EC2Client;
|
|
rds: RDSClient;
|
|
rdsData: RDSDataClient;
|
|
secretsManager: SecretsManagerClient;
|
|
logs: CloudWatchLogsClient;
|
|
ssm: SSMClient;
|
|
kms: KMSClient;
|
|
sfn: SFNClient;
|
|
elbv2: ElasticLoadBalancingV2Client;
|
|
eventbridge: EventBridgeClient;
|
|
sqs: SQSClient;
|
|
iam: IAMClient;
|
|
dynamodb: DynamoDBClient;
|
|
apiGateway: APIGatewayClient;
|
|
cloudformation: CloudFormationClient;
|
|
appConfig: AppConfigClient;
|
|
sns: SNSClient;
|
|
ecr: ECRClient;
|
|
codebuild: CodeBuildClient;
|
|
codepipeline: CodePipelineClient;
|
|
codedeploy: CodeDeployClient;
|
|
};
|
|
|
|
export type AwsClientName = keyof AwsClients;
|
|
|
|
const clientCache = new Map<string, AwsClients>();
|
|
|
|
function buildClients(accountId: string): AwsClients {
|
|
const base = {
|
|
region,
|
|
credentials: { accessKeyId: accountId, secretAccessKey: SECRET_ACCESS_KEY },
|
|
...(endpoint ? { endpoint, forcePathStyle: true } : {}),
|
|
};
|
|
return {
|
|
s3: new S3Client({ ...base, forcePathStyle: true }),
|
|
lambda: new LambdaClient(base),
|
|
eks: new EKSClient(base),
|
|
ec2: new EC2Client(base),
|
|
rds: new RDSClient(base),
|
|
rdsData: new RDSDataClient(base),
|
|
secretsManager: new SecretsManagerClient(base),
|
|
logs: new CloudWatchLogsClient(base),
|
|
ssm: new SSMClient(base),
|
|
kms: new KMSClient(base),
|
|
sfn: new SFNClient(base),
|
|
elbv2: new ElasticLoadBalancingV2Client(base),
|
|
eventbridge: new EventBridgeClient(base),
|
|
sqs: new SQSClient(base),
|
|
iam: new IAMClient(base),
|
|
dynamodb: new DynamoDBClient(base),
|
|
apiGateway: new APIGatewayClient(base),
|
|
cloudformation: new CloudFormationClient(base),
|
|
appConfig: new AppConfigClient(base),
|
|
sns: new SNSClient(base),
|
|
ecr: new ECRClient(base),
|
|
codebuild: new CodeBuildClient(base),
|
|
codepipeline: new CodePipelineClient(base),
|
|
codedeploy: new CodeDeployClient(base),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Return the cached AWS SDK client set bound to a given account. Clients are
|
|
* cheap to build but each holds connection state, so we memoize one set per
|
|
* resolved account id.
|
|
*/
|
|
export function awsClientsForAccount(accountId?: string | null): AwsClients {
|
|
const id = resolveAccountId(accountId);
|
|
let clients = clientCache.get(id);
|
|
if (!clients) {
|
|
clients = buildClients(id);
|
|
clientCache.set(id, clients);
|
|
}
|
|
return clients;
|
|
}
|
|
|
|
// Default-account singletons, kept for surfaces that are not yet account-aware:
|
|
// the dedicated EC2, EKS, RDS and Secrets Manager routes. These ignore the
|
|
// x-floci-account-id header and always serve the default account, so they do not
|
|
// follow the console's active account. The Cloud Explorer proxy resolves clients
|
|
// per request via awsClientsForAccount() instead.
|
|
export const awsClients = awsClientsForAccount(defaultAccountId());
|
|
|
|
export const s3 = awsClients.s3;
|
|
export const lambda = awsClients.lambda;
|
|
export const eks = awsClients.eks;
|
|
export const ec2 = awsClients.ec2;
|
|
export const rds = awsClients.rds;
|
|
export const secretsManager = awsClients.secretsManager;
|
|
export const eventbridge = awsClients.eventbridge;
|
|
export const sqs = awsClients.sqs;
|
|
export const iam = awsClients.iam;
|
|
export const dynamodb = awsClients.dynamodb;
|
|
export const apiGateway = awsClients.apiGateway;
|
|
export const cloudformation = awsClients.cloudformation;
|
|
export const ecr = awsClients.ecr;
|