Skip to content

Commit

Permalink
Fix: amplify init access key validation (#6986) (ref #4128 #6617)
Browse files Browse the repository at this point in the history
  • Loading branch information
gitaalekhyapaul authored Apr 19, 2021
1 parent c7b2406 commit 4761406
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 49 deletions.
12 changes: 12 additions & 0 deletions packages/amplify-cli-core/src/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,15 @@ export class AppAlreadyDeployedError extends Error {}
export class SchemaDoesNotExistError extends Error {}
export class AngularConfigNotFoundError extends Error {}
export class AppIdMismatchError extends Error {}
export class NotInitializedError extends Error {
public constructor() {
super();
this.name = 'NotInitializedError';
this.message = `
No Amplify backend project files detected within this folder. Either initialize a new Amplify project or pull an existing project.
- "amplify init" to initialize a new Amplify project
- "amplify pull <app-id>" to pull your existing Amplify project. Find the <app-id> in the AWS Console or Amplify Admin UI.
`;
this.stack = undefined;
}
}
14 changes: 2 additions & 12 deletions packages/amplify-cli-core/src/state-manager/pathManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as path from 'path';
import * as fs from 'fs-extra';
import { homedir } from 'os';
import { NotInitializedError } from '../errors';

export const PathConstants = {
// in home directory
Expand Down Expand Up @@ -162,7 +163,7 @@ export class PathManager {
return path.normalize(path.join(projectPath, ...segments));
}

throw this.createNotInitializedError();
throw new NotInitializedError();
};

private validateProjectPath = (projectPath: string): boolean => {
Expand Down Expand Up @@ -196,17 +197,6 @@ export class PathManager {

return undefined;
};

private createNotInitializedError = (): Error => {
const error = new Error(
"You are not working inside a valid Amplify project.\nUse 'amplify init' in the root of your app directory to initialize your project, or 'amplify pull' to pull down an existing project.",
);

error.name = 'NotInitialized';
error.stack = undefined;

return error;
};
}

export const pathManager = new PathManager();
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import { stateManager } from 'amplify-cli-core';
import { NotInitializedError, stateManager } from 'amplify-cli-core';

export function getProjectMeta() {
if (!stateManager.metaFileExists()) {
const error = new Error(
"You are not working inside a valid Amplify project.\nUse 'amplify init' in the root of your app directory to initialize your project, or 'amplify pull' to pull down an existing project.",
);

error.name = 'NotInitialized';
error.stack = undefined;

throw error;
throw new NotInitializedError();
}

return stateManager.getMeta();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as path from 'path';
import * as fs from 'fs-extra';
import { homedir } from 'os';
import { amplifyCLIConstants } from './constants';
import { NotInitializedError } from 'amplify-cli-core';

/* Helpers */

Expand Down Expand Up @@ -50,7 +51,7 @@ export function getAmplifyDirPath(projectPath?) {
if (projectPath) {
return path.normalize(path.join(projectPath, amplifyCLIConstants.AmplifyCLIDirName));
}
throw createNotInitializedError();
throw new NotInitializedError();
}

// ///////////////////level 1
Expand All @@ -73,7 +74,7 @@ export function getAmplifyRcFilePath(projectPath?) {
if (projectPath) {
return path.normalize(path.join(projectPath, '.amplifyrc'));
}
throw createNotInitializedError();
throw new NotInitializedError();
}

export function getGitIgnoreFilePath(projectPath?) {
Expand All @@ -83,7 +84,7 @@ export function getGitIgnoreFilePath(projectPath?) {
if (projectPath) {
return path.normalize(path.join(projectPath, '.gitignore'));
}
throw createNotInitializedError();
throw new NotInitializedError();
}

// ///////////////////level 2
Expand Down Expand Up @@ -115,14 +116,3 @@ export function getAmplifyMetaFilePath(projectPath?) {
export function getCurrentAmplifyMetaFilePath(projectPath?) {
return path.normalize(path.join(getCurrentCloudBackendDirPath(projectPath), amplifyCLIConstants.amplifyMetaFileName));
}

function createNotInitializedError() {
const error = new Error(
"You are not working inside a valid Amplify project.\nUse 'amplify init' in the root of your app directory to initialize your project, or 'amplify pull' to pull down an existing project.",
);

error.name = 'NotInitialized';
error.stack = undefined;

return error;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getEnvInfo } from './get-env-info';
import { CLOUD_INITIALIZED, CLOUD_NOT_INITIALIZED, getCloudInitStatus } from './get-cloud-init-status';
import { ServiceName as FunctionServiceName, hashLayerResource } from 'amplify-category-function';
import { removeGetUserEndpoints } from '../amplify-helpers/remove-pinpoint-policy';
import { pathManager, stateManager, $TSMeta, $TSAny, Tag } from 'amplify-cli-core';
import { pathManager, stateManager, $TSMeta, $TSAny, Tag, NotInitializedError } from 'amplify-cli-core';

async function isBackendDirModifiedSinceLastPush(resourceName, category, lastPushTimeStamp, isLambdaLayer = false) {
// Pushing the resource for the first time hence no lastPushTimeStamp
Expand Down Expand Up @@ -355,14 +355,7 @@ export async function getResourceStatus(category?, resourceName?, providerName?,
} else if (amplifyProjectInitStatus === CLOUD_NOT_INITIALIZED) {
amplifyMeta = stateManager.getBackendConfig();
} else {
const error = new Error(
"You are not working inside a valid Amplify project.\nUse 'amplify init' in the root of your app directory to initialize your project, or 'amplify pull' to pull down an existing project.",
);

error.name = 'NotInitialized';
error.stack = undefined;

throw error;
throw new NotInitializedError();
}

let resourcesToBeCreated: any = getResourcesToBeCreated(amplifyMeta, currentAmplifyMeta, category, resourceName, filteredResources);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import {
profileNameQuestion,
removeProjectComfirmQuestion,
updateOrRemoveQuestion,
retryAuthConfig,
} from './question-flows/configuration-questions';
import { STS } from 'aws-sdk';

interface AwsConfig extends AwsSecrets {
useProfile?: boolean;
Expand Down Expand Up @@ -223,9 +225,34 @@ async function initialize(context: $TSContext, authConfig?: AuthFlowConfig) {
}
}

validateConfig(context);
await validateConfig(context);
if (!awsConfigInfo.configValidated) {
throw new Error('Invalid configuration settings');
context.print.error('Invalid configuration settings!');
const { retryConfirmation } = await prompt(retryAuthConfig);
if (retryConfirmation) {
// Cleaning up broken configurations
if (authConfig.type === 'admin') {
context.exeInfo.awsConfigInfo = {
configLevel: 'amplifyAdmin',
config: {},
};
} else if (authConfig.type === 'accessKeys') {
context.exeInfo.awsConfigInfo = {
configLevel: 'project',
config: { useProfile: false },
};
} else {
context.exeInfo.awsConfigInfo = {
configLevel: 'project',
config: defaultAWSConfig,
};
}

return initialize(context, authConfig);
} else {
context.print.error('Exiting...');
exitOnNextTick(1);
}
}

return context;
Expand All @@ -247,7 +274,7 @@ async function create(context: $TSContext) {
await promptForAuthConfig(context);
}

validateConfig(context);
await validateConfig(context);
if (awsConfigInfo.configValidated) {
persistLocalEnvConfig(context);
} else {
Expand All @@ -264,7 +291,7 @@ async function update(context: $TSContext) {
} else {
await promptForAuthConfig(context);
}
validateConfig(context);
await validateConfig(context);
if (awsConfigInfo.configValidated) {
updateProjectConfig(context);
} else {
Expand Down Expand Up @@ -387,6 +414,8 @@ async function promptForAuthConfig(context: $TSContext, authConfig?: AuthFlowCon
? obfuscateUtil.obfuscate(awsConfigInfo.config.secretAccessKey)
: constants.DefaultAWSSecretAccessKey,
awsConfigInfo.config.region || constants.DefaultAWSRegion,
validateAccessKeyId,
validateSecretAccessKey,
obfuscateUtil.transform,
),
);
Expand All @@ -399,7 +428,7 @@ async function promptForAuthConfig(context: $TSContext, authConfig?: AuthFlowCon
awsConfigInfo.config.region = answers.region;
}

function validateConfig(context: $TSContext) {
async function validateConfig(context: $TSContext) {
const { awsConfigInfo } = context.exeInfo;
awsConfigInfo.configValidated = false;
if (awsConfigInfo.configLevel === 'general' || awsConfigInfo.configLevel === 'amplifyAdmin') {
Expand All @@ -417,6 +446,17 @@ function validateConfig(context: $TSContext) {
awsConfigInfo.config.secretAccessKey !== constants.DefaultAWSSecretAccessKey &&
awsConfigInfo.config.region &&
awsRegions.regions.includes(awsConfigInfo.config.region);
const sts = new STS({
credentials: {
accessKeyId: awsConfigInfo.config.accessKeyId,
secretAccessKey: awsConfigInfo.config.secretAccessKey,
},
});
try {
await sts.getCallerIdentity({}).promise();
} catch (err) {
awsConfigInfo.configValidated = false;
}
}
}
return context;
Expand Down Expand Up @@ -841,3 +881,16 @@ async function askAuthType(isAdminAvailable: boolean = false): Promise<AuthFlow>

return authChoice;
}

// Regex adapted from: https://aws.amazon.com/blogs/security/a-safer-way-to-distribute-aws-credentials-to-ec2/

function validateAccessKeyId(input: $TSAny): string | boolean {
const INVALID_ACCESS_KEY_ID = 'Access Key ID must be 20 characters, and uppercase alphanumeric only.';
const accessKeyIdRegex = /^[A-Z0-9]{20}$/;
return accessKeyIdRegex.test(input) ? true : INVALID_ACCESS_KEY_ID;
}
function validateSecretAccessKey(input: $TSAny): string | boolean {
const INVALID_SECRET_ACCESS_KEY = 'Secret Access Key must be 40 characters, and base-64 string only.';
const secretAccessKeyRegex = /^[A-Za-z0-9/+=]{40}$/;
return secretAccessKeyRegex.test(input) ? true : INVALID_SECRET_ACCESS_KEY;
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export function accessKeysQuestion(
accessKeyDefault: $TSAny,
secretAccessKeyDefault: $TSAny,
defaultRegion: string,
accessKeyValidator: $TSAny,
secretAccessKeyValidator: $TSAny,
transformer: $TSAny,
): (PasswordQuestion | ListQuestion)[] {
return [
Expand All @@ -34,6 +36,7 @@ export function accessKeysQuestion(
name: 'accessKeyId',
message: 'accessKeyId: ',
default: accessKeyDefault,
validate: accessKeyValidator,
transformer,
},
{
Expand All @@ -42,6 +45,7 @@ export function accessKeysQuestion(
name: 'secretAccessKey',
message: 'secretAccessKey: ',
default: secretAccessKeyDefault,
validate: secretAccessKeyValidator,
transformer,
},
{
Expand Down Expand Up @@ -79,3 +83,10 @@ export const updateOrRemoveQuestion: ListQuestion = {
],
default: 'cancel',
};

export const retryAuthConfig: ConfirmQuestion = {
type: 'confirm',
name: 'retryConfirmation',
message: 'Do you want to retry configuration?',
default: false,
};

0 comments on commit 4761406

Please sign in to comment.