Skip to content

Commit 9ab1c37

Browse files
authored
Avoid double processing of default cert bundle in AL2023
1 parent a242101 commit 9ab1c37

File tree

6 files changed

+187
-5
lines changed

6 files changed

+187
-5
lines changed

LambdaRuntimeDockerfiles/Images/net8/amd64/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ RUN dotnet build "Amazon.Lambda.RuntimeSupport.csproj" /p:ExecutableOutputType=t
4040
FROM builder AS publish
4141
RUN dotnet publish "Amazon.Lambda.RuntimeSupport.csproj" /p:ExecutableOutputType=true /p:GenerateDocumentationFile=false /p:TargetFrameworks=net8.0 -f net8.0 --runtime linux-x64 --self-contained false -p:PublishReadyToRun=true -c Release -o /app/publish
4242
RUN apt-get update && apt-get install -y dos2unix
43-
RUN dos2unix /app/publish/bootstrap.sh && \
44-
mv /app/publish/bootstrap.sh /app/publish/bootstrap && \
43+
RUN dos2unix /app/publish/bootstrap-al2023.sh && \
44+
mv /app/publish/bootstrap-al2023.sh /app/publish/bootstrap && \
4545
chmod +x /app/publish/bootstrap
4646

4747

LambdaRuntimeDockerfiles/Images/net8/arm64/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ RUN dotnet build "Amazon.Lambda.RuntimeSupport.csproj" /p:ExecutableOutputType=t
4040
FROM builder AS publish
4141
RUN dotnet publish "Amazon.Lambda.RuntimeSupport.csproj" /p:ExecutableOutputType=true /p:GenerateDocumentationFile=false /p:TargetFrameworks=net8.0 -f net8.0 --runtime linux-arm64 --self-contained false -p:PublishReadyToRun=true -c Release -o /app/publish
4242
RUN apt-get update && apt-get install -y dos2unix
43-
RUN dos2unix /app/publish/bootstrap.sh && \
44-
mv /app/publish/bootstrap.sh /app/publish/bootstrap && \
43+
RUN dos2unix /app/publish/bootstrap-al2023.sh && \
44+
mv /app/publish/bootstrap-al2023.sh /app/publish/bootstrap && \
4545
chmod +x /app/publish/bootstrap
4646

4747

LambdaRuntimeDockerfiles/SmokeTests/test/ImageFunction.SmokeTests/ImageFunctionTests.cs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,56 @@ public async Task ExceptionTests(string handler, string input, string expectedEr
114114
Assert.Equal(expectedErrorMessage, exception["errorMessage"].ToString());
115115
}
116116

117-
private async Task UpdateHandlerAsync(string handler)
117+
/// <summary>
118+
/// This test is checking the logic added to the bootstrap.sh to change the SSL_CERT_FILE
119+
/// environment variable for AL2023.
120+
/// </summary>
121+
/// <param name="envName"></param>
122+
/// <param name="expectedValue"></param>
123+
/// <param name="setValue"></param>
124+
/// <returns></returns>
125+
[Theory]
126+
#if NET8_0_OR_GREATER
127+
[InlineData("SSL_CERT_FILE", "\"/tmp/noop\"", null)]
128+
[InlineData("SSL_CERT_FILE", "\"/tmp/my-bundle\"", "/tmp/my-bundle")]
129+
#else
130+
[InlineData("SSL_CERT_FILE", "", null)]
131+
[InlineData("SSL_CERT_FILE", "/tmp/my-bundle", "/tmp/my-bundle")]
132+
#endif
133+
public async Task CheckEnvironmentVariable(string envName, string expectedValue, string setValue)
134+
{
135+
var envVariables = new Dictionary<string, string>();
136+
if(setValue != null)
137+
{
138+
envVariables[envName] = setValue;
139+
}
140+
141+
await UpdateHandlerAsync("ImageFunction::ImageFunction.Function::GetEnvironmentVariable", envVariables);
142+
143+
var payload = JsonConvert.SerializeObject(envName);
144+
var invokeResponse = await InvokeFunctionAsync(payload);
145+
146+
Assert.True(invokeResponse.HttpStatusCode == System.Net.HttpStatusCode.OK);
147+
Assert.True(invokeResponse.FunctionError == null, "Failed invoke with error: " + invokeResponse.FunctionError);
148+
149+
await using var responseStream = invokeResponse.Payload;
150+
var actualValue = new StreamReader(responseStream).ReadToEnd();
151+
152+
Assert.Equal(expectedValue, actualValue);
153+
}
154+
155+
private async Task UpdateHandlerAsync(string handler, Dictionary<string, string> environmentVariables = null)
118156
{
119157
var updateFunctionConfigurationRequest = new UpdateFunctionConfigurationRequest
120158
{
121159
FunctionName = _functionName,
122160
ImageConfig = new ImageConfig()
123161
{
124162
Command = {handler},
163+
},
164+
Environment = new Amazon.Lambda.Model.Environment
165+
{
166+
Variables = environmentVariables ?? new Dictionary<string, string>()
125167
}
126168
};
127169
await _lambdaClient.UpdateFunctionConfigurationAsync(updateFunctionConfigurationRequest);

LambdaRuntimeDockerfiles/SmokeTests/test/ImageFunction/Function.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ public string VerifyTzData(ILambdaContext lambdaContext)
128128
return SuccessResult;
129129
}
130130

131+
public string GetEnvironmentVariable(string name)
132+
{
133+
return Environment.GetEnvironmentVariable(name) ?? string.Empty;
134+
}
135+
131136
#endregion
132137

133138
#region Private methods

Libraries/src/Amazon.Lambda.RuntimeSupport/Amazon.Lambda.RuntimeSupport.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,8 @@
4545
<None Update="bootstrap.sh">
4646
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
4747
</None>
48+
<None Update="bootstrap-al2023.sh">
49+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
50+
</None>
4851
</ItemGroup>
4952
</Project>
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#!/bin/bash
2+
3+
# .NET on Linux uses OpenSSL to handle certificates. The .NET runtime will load the certs by first reading
4+
# the default cert bundle file which can be overriden by the SSL_CERT_FILE env var. Then it will load the
5+
# certs in the default cert directory which can be overriden by the SSL_CERT_DIR env var. On AL2023
6+
# The default cert bundle file, via symbolic links, resolves to being in a file under the default cert directory.
7+
# This means the default cert bundle file is double loaded causing a cold start performance hit. This logic
8+
# sets the SSL_CERT_FILE to a noop file if SSL_CERT_FILE hasn't been explicitly
9+
# set. This avoid the double load of the default cert bundle file.
10+
if [ -z "${SSL_CERT_FILE}"]; then
11+
export SSL_CERT_FILE="/tmp/noop"
12+
fi
13+
14+
# This script is used to locate 2 files in the /var/task folder, where the end-user assembly is located
15+
# The 2 files are <assembly name>.deps.json and <assembly name>.runtimeconfig.json
16+
# These files are used to add the end-user assembly into context and make the code reachable to the dotnet process
17+
# Since the file names are not known in advance, we use this shell script to find the files and pass them to the dotnet process as parameters
18+
# You can improve cold-start performance by setting the LAMBDA_DOTNET_MAIN_ASSEMBLY environment variable and specifying the assembly name
19+
# LAMBDA_TASK_ROOT is inherited from the Lambda execution environment/base image as "/var/task", but can be overridden for use in custom images.
20+
if [ -z "${LAMBDA_TASK_ROOT}" ]; then
21+
echo "Error: Environment variable LAMBDA_TASK_ROOT needs to be defined in order for the Lambda Runtime to load the function handler to be executed." 1>&2
22+
exit 101
23+
fi
24+
25+
if [ ! -d "${LAMBDA_TASK_ROOT}" ] | [ -z "$(ls -A ${LAMBDA_TASK_ROOT})" ]; then
26+
echo "Error: .NET binaries for Lambda function are not correctly installed in the ${LAMBDA_TASK_ROOT} directory of the image when the image was built. The ${LAMBDA_TASK_ROOT} directory is missing." 1>&2
27+
exit 102
28+
fi
29+
30+
# Get version of Lambda .NET runtime if available
31+
export "$(grep LAMBDA_RUNTIME_NAME /var/runtime/runtime-release 2>/dev/null || echo LAMBDA_RUNTIME_NAME=dotnet_custom)"
32+
export AWS_EXECUTION_ENV="AWS_Lambda_${LAMBDA_RUNTIME_NAME}"
33+
34+
export DOTNET_ROOT="/var/lang/bin"
35+
DOTNET_BIN="${DOTNET_ROOT}/dotnet"
36+
DOTNET_EXEC="exec"
37+
DOTNET_ARGS=()
38+
EXECUTABLE_BINARY_EXIST=false
39+
40+
LAMBDA_HANDLER=""
41+
# Command-line parameter has precedence over "_HANDLER" environment variable
42+
if [ -n "${1}" ]; then
43+
LAMBDA_HANDLER="${1}"
44+
elif [ -n "${_HANDLER}" ]; then
45+
LAMBDA_HANDLER="${_HANDLER}"
46+
else
47+
echo "Error: No Lambda Handler function was specified." 1>&2
48+
exit 103
49+
fi
50+
51+
HANDLER_COL_INDEX=$(expr index "${LAMBDA_HANDLER}" ":")
52+
53+
if [[ "${HANDLER_COL_INDEX}" == 0 ]]; then
54+
EXECUTABLE_ASSEMBLY="${LAMBDA_TASK_ROOT}/${LAMBDA_HANDLER}"
55+
EXECUTABLE_BINARY="${LAMBDA_TASK_ROOT}/${LAMBDA_HANDLER}"
56+
if [[ "${EXECUTABLE_ASSEMBLY}" != *.dll ]]; then
57+
EXECUTABLE_ASSEMBLY="${EXECUTABLE_ASSEMBLY}.dll"
58+
fi
59+
if [[ -f "${EXECUTABLE_ASSEMBLY}" ]]; then
60+
DOTNET_ARGS+=("${EXECUTABLE_ASSEMBLY}")
61+
elif [[ -f "${EXECUTABLE_BINARY}" ]]; then
62+
EXECUTABLE_BINARY_EXIST=true
63+
else
64+
echo "Error: executable assembly ${EXECUTABLE_ASSEMBLY} or binary ${EXECUTABLE_BINARY} not found." 1>&2
65+
exit 104
66+
fi
67+
else
68+
if [ -n "${LAMBDA_DOTNET_MAIN_ASSEMBLY}" ]; then
69+
if [[ "${LAMBDA_DOTNET_MAIN_ASSEMBLY}" == *.dll ]]; then
70+
ASSEMBLY_NAME="${LAMBDA_DOTNET_MAIN_ASSEMBLY::-4}"
71+
else
72+
ASSEMBLY_NAME="${LAMBDA_DOTNET_MAIN_ASSEMBLY}"
73+
fi
74+
else
75+
ASSEMBLY_NAME="${LAMBDA_HANDLER::${HANDLER_COL_INDEX}-1}"
76+
fi
77+
78+
DEPS_FILE="${LAMBDA_TASK_ROOT}/${ASSEMBLY_NAME}.deps.json"
79+
if ! [ -f "${DEPS_FILE}" ]; then
80+
DEPS_FILES=( "${LAMBDA_TASK_ROOT}"/*.deps.json )
81+
82+
# Check if there were any matches to the *.deps.json glob, and that the glob was resolved
83+
# This makes the matching independent from the global `nullopt` shopt's value (https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html)
84+
if [ "${#DEPS_FILES[@]}" -ne 1 ] || echo "${DEPS_FILES[0]}" | grep -q -F '*'; then
85+
echo "Error: .NET binaries for Lambda function are not correctly installed in the ${LAMBDA_TASK_ROOT} directory of the image when the image was built. The ${LAMBDA_TASK_ROOT} directory is missing the required .deps.json file." 1>&2
86+
exit 105
87+
fi
88+
DEPS_FILE="${DEPS_FILES[0]}"
89+
fi
90+
91+
RUNTIMECONFIG_FILE="${LAMBDA_TASK_ROOT}/${ASSEMBLY_NAME}.runtimeconfig.json"
92+
if ! [ -f "${RUNTIMECONFIG_FILE}" ]; then
93+
RUNTIMECONFIG_FILES=( "${LAMBDA_TASK_ROOT}"/*.runtimeconfig.json )
94+
95+
# Check if there were any matches to the *.runtimeconfig.json glob, and that the glob was resolved
96+
# This makes the matching independent from the global `nullopt` shopt's value (https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html)
97+
if [ "${#RUNTIMECONFIG_FILES[@]}" -ne 1 ] || echo "${RUNTIMECONFIG_FILES[0]}" | grep -q -F '*'; then
98+
echo "Error: .NET binaries for Lambda function are not correctly installed in the ${LAMBDA_TASK_ROOT} directory of the image when the image was built. The ${LAMBDA_TASK_ROOT} directory is missing the required .runtimeconfig.json file." 1>&2
99+
exit 106
100+
fi
101+
RUNTIMECONFIG_FILE="${RUNTIMECONFIG_FILES[0]}"
102+
fi
103+
104+
DOTNET_ARGS+=("--depsfile" "${DEPS_FILE}"
105+
"--runtimeconfig" "${RUNTIMECONFIG_FILE}"
106+
"/var/runtime/Amazon.Lambda.RuntimeSupport.dll" "${LAMBDA_HANDLER}")
107+
fi
108+
109+
110+
# To support runtime wrapper scripts
111+
# https://docs.aws.amazon.com/lambda/latest/dg/runtimes-modify.html#runtime-wrapper
112+
if [ -z "${AWS_LAMBDA_EXEC_WRAPPER}" ]; then
113+
if [ ${EXECUTABLE_BINARY_EXIST} = true ]; then
114+
exec "${EXECUTABLE_BINARY}"
115+
else
116+
exec "${DOTNET_BIN}" "${DOTNET_EXEC}" "${DOTNET_ARGS[@]}"
117+
fi
118+
else
119+
if [ ! -f "${AWS_LAMBDA_EXEC_WRAPPER}" ]; then
120+
echo "${AWS_LAMBDA_EXEC_WRAPPER}: does not exist"
121+
exit 127
122+
fi
123+
if [ ! -x "${AWS_LAMBDA_EXEC_WRAPPER}" ]; then
124+
echo "${AWS_LAMBDA_EXEC_WRAPPER}: is not an executable"
125+
exit 126
126+
fi
127+
if [ ${EXECUTABLE_BINARY_EXIST} = true ]; then
128+
exec -- "${AWS_LAMBDA_EXEC_WRAPPER}" "${EXECUTABLE_BINARY}"
129+
else
130+
exec -- "${AWS_LAMBDA_EXEC_WRAPPER}" "${DOTNET_BIN}" "${DOTNET_EXEC}" "${DOTNET_ARGS[@]}"
131+
fi
132+
fi

0 commit comments

Comments
 (0)