Skip to content

Commit e7df1c6

Browse files
authored
Validate SAM CLI on detect (#1465)
SAM CLI now validates detected executables. This prevents non-SAM executables that match the SAM CLI's signature from accidentally getting picked up if it comes first in the user's PATH.
1 parent b772c76 commit e7df1c6

File tree

12 files changed

+136
-94
lines changed

12 files changed

+136
-94
lines changed

src/shared/sam/cli/samCliBuild.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import { fileExists } from '../../filesystemUtilities'
77
import { getLogger, Logger } from '../../logger'
88
import { logAndThrowIfUnexpectedExitCode, SamCliProcessInvoker } from './samCliInvokerUtils'
9-
import { DefaultSamCliProcessInvoker } from './samCliInvoker'
109
import { pushIf } from '../../utilities/collectionUtils'
1110
import { localize } from '../../utilities/vsCodeUtils'
1211

@@ -75,7 +74,6 @@ export class SamCliBuildInvocation {
7574
private readonly args: SamCliBuildInvocationArguments,
7675
private readonly context: { file: FileFunctions } = { file: getDefaultFileFunctions() }
7776
) {
78-
this.args.invoker = this.args.invoker ?? new DefaultSamCliProcessInvoker()
7977
this.args.useContainer = !!this.args.useContainer
8078
this.args.skipPullImage = !!this.args.skipPullImage
8179
}

src/shared/sam/cli/samCliConfiguration.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import * as vscode from 'vscode'
77
import { SettingsConfiguration } from '../../settingsConfiguration'
8-
import { SamCliLocationProvider } from './samCliLocator'
98

109
export interface SamCliConfiguration {
1110
/** Gets the current SAM CLI location from the VSCode settings store. */
@@ -34,9 +33,12 @@ export interface SamCliConfiguration {
3433
export class DefaultSamCliConfiguration implements SamCliConfiguration {
3534
public static readonly CONFIGURATION_KEY_SAMCLI_LOCATION: string = 'samcli.location'
3635
private readonly _configuration: SettingsConfiguration
37-
private readonly _samCliLocationProvider: SamCliLocationProvider
36+
private readonly _samCliLocationProvider: { getLocation(): Promise<string | undefined> }
3837

39-
public constructor(configuration: SettingsConfiguration, samCliLocationProvider: SamCliLocationProvider) {
38+
public constructor(
39+
configuration: SettingsConfiguration,
40+
samCliLocationProvider: { getLocation(): Promise<string | undefined> }
41+
) {
4042
this._configuration = configuration
4143
this._samCliLocationProvider = samCliLocationProvider
4244
}

src/shared/sam/cli/samCliContext.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { SettingsConfiguration } from '../../settingsConfiguration'
77
import { DefaultSamCliConfiguration } from './samCliConfiguration'
8-
import { DefaultSamCliProcessInvoker, SamCliProcessInvokerContext } from './samCliInvoker'
8+
import { DefaultSamCliProcessInvoker } from './samCliInvoker'
99
import { SamCliProcessInvoker } from './samCliInvokerUtils'
1010
import { DefaultSamCliLocationProvider } from './samCliLocator'
1111
import { throwAndNotifyIfInvalid } from './samCliValidationUtils'
@@ -58,13 +58,9 @@ function makeSamCliContext(): SamCliContext {
5858
settingsConfiguration,
5959
new DefaultSamCliLocationProvider()
6060
)
61+
const invoker = new DefaultSamCliProcessInvoker({ preloadedConfig: samCliConfiguration })
6162

62-
const invokerContext: SamCliProcessInvokerContext = {
63-
cliConfig: samCliConfiguration,
64-
}
65-
const invoker = new DefaultSamCliProcessInvoker(invokerContext)
66-
67-
const validatorContext = new DefaultSamCliValidatorContext(samCliConfiguration, invoker)
63+
const validatorContext = new DefaultSamCliValidatorContext(samCliConfiguration)
6864
const validator = new DefaultSamCliValidator(validatorContext)
6965

7066
const context: SamCliContext = {

src/shared/sam/cli/samCliInfo.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import { getLogger, Logger } from '../../logger'
7+
import { SamCliConfiguration } from './samCliConfiguration'
78
import { DefaultSamCliProcessInvoker } from './samCliInvoker'
89
import { logAndThrowIfUnexpectedExitCode, SamCliProcessInvoker } from './samCliInvokerUtils'
910

@@ -15,7 +16,29 @@ export interface SamCliInfoResponse {
1516
}
1617

1718
export class SamCliInfoInvocation {
18-
public constructor(private readonly invoker: SamCliProcessInvoker = new DefaultSamCliProcessInvoker()) {}
19+
private readonly invoker: SamCliProcessInvoker
20+
public constructor(params: {
21+
invoker?: SamCliProcessInvoker
22+
preloadedConfig?: SamCliConfiguration
23+
locationProvider?: { getLocation(): Promise<string | undefined> }
24+
}) {
25+
if (
26+
(params.invoker && params.preloadedConfig) ||
27+
(params.invoker && params.locationProvider) ||
28+
(params.preloadedConfig && params.locationProvider)
29+
) {
30+
throw new Error('Invalid constructor args for SamCliInfoInvocation')
31+
}
32+
if (params.invoker) {
33+
this.invoker = params.invoker
34+
} else if (params.preloadedConfig) {
35+
this.invoker = new DefaultSamCliProcessInvoker({ preloadedConfig: params.preloadedConfig })
36+
} else if (params.locationProvider) {
37+
this.invoker = new DefaultSamCliProcessInvoker({ locationProvider: params.locationProvider })
38+
} else {
39+
throw new Error('Invalid constructor args for SamCliInfoInvocation')
40+
}
41+
}
1942

2043
public async execute(): Promise<SamCliInfoResponse> {
2144
const childProcessResult = await this.invoker.invoke({ arguments: ['--info'] })

src/shared/sam/cli/samCliInvoker.ts

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,50 +3,46 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { extensionSettingsPrefix } from '../../constants'
76
import { getLogger } from '../../logger'
8-
import { DefaultSettingsConfiguration } from '../../settingsConfiguration'
97
import { ChildProcess, ChildProcessResult } from '../../utilities/childProcess'
10-
import { DefaultSamCliConfiguration, SamCliConfiguration } from './samCliConfiguration'
118
import {
129
makeRequiredSamCliProcessInvokeOptions,
1310
SamCliProcessInvokeOptions,
1411
SamCliProcessInvoker,
1512
} from './samCliInvokerUtils'
16-
import { DefaultSamCliLocationProvider } from './samCliLocator'
1713

1814
import * as nls from 'vscode-nls'
15+
import { DefaultSamCliConfiguration, SamCliConfiguration } from './samCliConfiguration'
16+
import { DefaultSettingsConfiguration } from '../../settingsConfiguration'
17+
import { extensionSettingsPrefix } from '../../constants'
1918
const localize = nls.loadMessageBundle()
2019

21-
export interface SamCliProcessInvokerContext {
22-
cliConfig: SamCliConfiguration
23-
}
24-
25-
export class DefaultSamCliProcessInvokerContext implements SamCliProcessInvokerContext {
26-
public cliConfig: SamCliConfiguration = new DefaultSamCliConfiguration(
27-
new DefaultSettingsConfiguration(extensionSettingsPrefix),
28-
new DefaultSamCliLocationProvider()
29-
)
30-
}
31-
32-
export function resolveSamCliProcessInvokerContext(
33-
params: Partial<SamCliProcessInvokerContext> = {}
34-
): SamCliProcessInvokerContext {
35-
const defaults = new DefaultSamCliProcessInvokerContext()
36-
37-
return {
38-
cliConfig: params.cliConfig || defaults.cliConfig,
39-
}
40-
}
41-
4220
/**
4321
* Yet another `sam` CLI wrapper.
4422
*
4523
* TODO: Merge this with `DefaultSamLocalInvokeCommand`.
4624
*/
4725
export class DefaultSamCliProcessInvoker implements SamCliProcessInvoker {
4826
private childProcess?: ChildProcess
49-
public constructor(private readonly context: SamCliProcessInvokerContext = resolveSamCliProcessInvokerContext()) {}
27+
private readonly context: SamCliConfiguration
28+
public constructor(params: {
29+
preloadedConfig?: SamCliConfiguration
30+
locationProvider?: { getLocation(): Promise<string | undefined> }
31+
}) {
32+
if (params.preloadedConfig && params.locationProvider) {
33+
throw new Error('Invalid constructor args for DefaultSamCliProcessInvoker')
34+
}
35+
if (params.preloadedConfig) {
36+
this.context = params.preloadedConfig
37+
} else if (params.locationProvider) {
38+
this.context = new DefaultSamCliConfiguration(
39+
new DefaultSettingsConfiguration(extensionSettingsPrefix),
40+
params.locationProvider
41+
)
42+
} else {
43+
throw new Error('Invalid constructor args for DefaultSamCliProcessInvoker')
44+
}
45+
}
5046

5147
public stop(): void {
5248
if (!this.childProcess) {
@@ -59,7 +55,7 @@ export class DefaultSamCliProcessInvoker implements SamCliProcessInvoker {
5955
const invokeOptions = makeRequiredSamCliProcessInvokeOptions(options)
6056
const logger = getLogger()
6157

62-
const sam = await this.context.cliConfig.getOrDetectSamCli()
58+
const sam = await this.context.getOrDetectSamCli()
6359
if (!sam.path) {
6460
logger.warn('SAM CLI not found and not configured')
6561
} else if (sam.autoDetected) {

src/shared/sam/cli/samCliLocalInvoke.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import { fileExists } from '../../filesystemUtilities'
1010
import { getLogger, Logger } from '../../logger'
1111
import { ChildProcess } from '../../utilities/childProcess'
1212
import { Timeout } from '../../utilities/timeoutUtils'
13-
import { DefaultSamCliProcessInvokerContext, SamCliProcessInvokerContext } from './samCliInvoker'
1413
import { removeAnsi } from '../../utilities/textUtilities'
1514
import { ext } from '../../extensionGlobals'
15+
import { DefaultSamCliProcessInvokerContext, SamCliProcessInvokerContext } from './samCliProcessInvokerContext'
1616

1717
const localize = nls.loadMessageBundle()
1818

src/shared/sam/cli/samCliLocator.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import * as path from 'path'
77
import { EnvironmentVariables } from '../../environmentVariables'
88
import * as filesystemUtilities from '../../filesystemUtilities'
99
import { getLogger, Logger } from '../../logger'
10+
import { SamCliInfoInvocation } from './samCliInfo'
11+
import { DefaultSamCliValidator, SamCliValidatorContext, SamCliVersionValidation } from './samCliValidator'
1012

1113
export interface SamCliLocationProvider {
1214
getLocation(): Promise<string | undefined>
@@ -69,8 +71,29 @@ abstract class BaseSamCliLocator {
6971

7072
for (const fullPath of fullPaths) {
7173
this.logger.verbose(`Searching for SAM CLI in: ${fullPath}`)
74+
const context: SamCliValidatorContext = {
75+
samCliLocation: async () => fullPath,
76+
getSamCliExecutableId: async () => 'bogus',
77+
getSamCliInfo: async () => {
78+
const samCliInfo = new SamCliInfoInvocation({
79+
locationProvider: { getLocation: async () => fullPath },
80+
})
81+
82+
return await samCliInfo.execute()
83+
},
84+
}
85+
const validator = new DefaultSamCliValidator(context)
7286
if (await filesystemUtilities.fileExists(fullPath)) {
73-
return fullPath
87+
try {
88+
const validationResult = await validator.getVersionValidatorResult()
89+
if (validationResult.validation === SamCliVersionValidation.Valid) {
90+
return fullPath
91+
}
92+
this.logger.info(`Found invalid SAM executable (${validationResult.validation}): ${fullPath}`)
93+
} catch (e) {
94+
const err = e as Error
95+
this.logger.error(err)
96+
}
7497
}
7598
}
7699

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*!
2+
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { extensionSettingsPrefix } from '../../constants'
7+
import { DefaultSettingsConfiguration } from '../../settingsConfiguration'
8+
import { DefaultSamCliConfiguration, SamCliConfiguration } from './samCliConfiguration'
9+
import { DefaultSamCliLocationProvider } from './samCliLocator'
10+
11+
export interface SamCliProcessInvokerContext {
12+
cliConfig: SamCliConfiguration
13+
}
14+
15+
export class DefaultSamCliProcessInvokerContext implements SamCliProcessInvokerContext {
16+
public cliConfig: SamCliConfiguration = new DefaultSamCliConfiguration(
17+
new DefaultSettingsConfiguration(extensionSettingsPrefix),
18+
new DefaultSamCliLocationProvider()
19+
)
20+
}

src/shared/sam/cli/samCliValidator.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { stat } from 'fs-extra'
77
import * as semver from 'semver'
88
import { SamCliConfiguration } from './samCliConfiguration'
99
import { SamCliInfoInvocation, SamCliInfoResponse } from './samCliInfo'
10-
import { SamCliProcessInvoker } from './samCliInvokerUtils'
1110

1211
export const MINIMUM_SAM_CLI_VERSION_INCLUSIVE = '0.47.0'
1312
export const MINIMUM_SAM_CLI_VERSION_INCLUSIVE_FOR_IMAGE_SUPPORT = '1.13.0'
@@ -129,10 +128,7 @@ export class DefaultSamCliValidator implements SamCliValidator {
129128
}
130129

131130
export class DefaultSamCliValidatorContext implements SamCliValidatorContext {
132-
public constructor(
133-
private readonly samCliConfiguration: SamCliConfiguration,
134-
private readonly invoker: SamCliProcessInvoker
135-
) {}
131+
public constructor(private readonly samCliConfiguration: SamCliConfiguration) {}
136132

137133
public async samCliLocation(): Promise<string> {
138134
return (await this.samCliConfiguration.getOrDetectSamCli()).path
@@ -151,7 +147,7 @@ export class DefaultSamCliValidatorContext implements SamCliValidatorContext {
151147
}
152148

153149
public async getSamCliInfo(): Promise<SamCliInfoResponse> {
154-
const samCliInfo = new SamCliInfoInvocation(this.invoker)
150+
const samCliInfo = new SamCliInfoInvocation({ preloadedConfig: this.samCliConfiguration })
155151

156152
return await samCliInfo.execute()
157153
}

src/shared/sam/localLambdaRunner.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { getTemplate, getTemplateResource, isImageLambdaConfig } from '../../lam
1313
import { getFamily, RuntimeFamily } from '../../lambda/models/samLambdaRuntime'
1414
import { ExtContext } from '../extensions'
1515
import { getLogger } from '../logger'
16-
import { SettingsConfiguration } from '../settingsConfiguration'
16+
import { DefaultSettingsConfiguration, SettingsConfiguration } from '../settingsConfiguration'
1717
import * as telemetry from '../telemetry/telemetry'
1818
import { SamTemplateGenerator } from '../templates/sam/samTemplateGenerator'
1919
import * as pathutil from '../utilities/pathUtils'
@@ -25,10 +25,14 @@ import { SamCliLocalInvokeInvocation, SamCliLocalInvokeInvocationArguments } fro
2525
import { SamLaunchRequestArgs } from './debugger/awsSamDebugger'
2626
import { asEnvironmentVariables } from '../../credentials/credentialsUtilities'
2727
import { buildSamCliStartApiArguments } from './cli/samCliStartApi'
28-
import { DefaultSamCliProcessInvoker, DefaultSamCliProcessInvokerContext } from './cli/samCliInvoker'
28+
import { DefaultSamCliProcessInvoker } from './cli/samCliInvoker'
2929
import { APIGatewayProperties } from './debugger/awsSamDebugConfiguration.gen'
3030
import { ChildProcess } from '../utilities/childProcess'
3131
import { ext } from '../extensionGlobals'
32+
import { DefaultSamCliProcessInvokerContext } from './cli/samCliProcessInvokerContext'
33+
import { DefaultSamCliConfiguration } from './cli/samCliConfiguration'
34+
import { extensionSettingsPrefix } from '../constants'
35+
import { DefaultSamCliLocationProvider } from './cli/samCliLocator'
3236

3337
const localize = nls.loadMessageBundle()
3438

@@ -165,7 +169,12 @@ export async function invokeLambdaFunction(
165169
)
166170
}
167171

168-
const processInvoker = new DefaultSamCliProcessInvoker()
172+
const processInvoker = new DefaultSamCliProcessInvoker({
173+
preloadedConfig: new DefaultSamCliConfiguration(
174+
new DefaultSettingsConfiguration(extensionSettingsPrefix),
175+
new DefaultSamCliLocationProvider()
176+
),
177+
})
169178

170179
getLogger('channel').info(localize('AWS.output.building.sam.application', 'Building SAM application...'))
171180
const samBuildOutputFolder = path.join(config.baseBuildDir!, 'output')

0 commit comments

Comments
 (0)