AWS lambda documentation change
Summary
Expanded Rust handler documentation with detailed setup instructions, example code for S3 integration, environment variable usage, and handler best practices
Security assessment
The changes add documentation about secure practices like environment variable handling and execution role requirements, but there's no evidence of addressing a specific security vulnerability. The example demonstrates secure AWS SDK usage and error handling for environment variables, which are security-related features.
Diff
diff --git a/lambda/latest/dg/rust-handler.md b/lambda/latest/dg/rust-handler.md index c43bf2114..95b886f54 100644 --- a/lambda/latest/dg/rust-handler.md +++ b/lambda/latest/dg/rust-handler.md @@ -5 +5 @@ -Rust handler basicsUsing shared stateCode best practices for Rust Lambda functions +Setting up your Rust handler projectExample Rust Lambda function codeValid class definitions for Rust handlersHandler naming conventionsDefining and accessing the input event objectAccessing and using the Lambda context objectUsing the AWS SDK for Rust in your handlerAccessing environment variablesUsing shared stateCode best practices for Rust Lambda functions @@ -7 +7 @@ Rust handler basicsUsing shared stateCode best practices for Rust Lambda functio -# Define Lambda function handler in Rust +# Define Lambda function handlers in Rust @@ -14,0 +15,2 @@ The Lambda function _handler_ is the method in your function code that processes +This page describes how to work with Lambda function handlers in Rust, including project initialization, naming conventions, and best practices. This page also includes an example of a Rust Lambda function that takes in information about an order, produces a text file receipt, and puts this file in an Amazon Simple Storage Service (S3) bucket. For more information about how to deploy your function after writing it, see [Deploy Rust Lambda functions with .zip file archives](./rust-package.html). + @@ -17 +19,15 @@ The Lambda function _handler_ is the method in your function code that processes - * Rust handler basics + * Setting up your Rust handler project + + * Example Rust Lambda function code + + * Valid class definitions for Rust handlers + + * Handler naming conventions + + * Defining and accessing the input event object + + * Accessing and using the Lambda context object + + * Using the AWS SDK for Rust in your handler + + * Accessing environment variables @@ -26 +42,11 @@ The Lambda function _handler_ is the method in your function code that processes -## Rust handler basics +## Setting up your Rust handler project + +When working with Lambda functions in Rust, the process involves writing your code, compiling it, and deploying the compiled artifacts to Lambda. The simplest way to set up a Lambda handler project in Rust is to use the [AWS Lambda Runtime for Rust](https://github.com/awslabs/aws-lambda-rust-runtime). Despite its name, the AWS Lambda Runtime for Rust is not a managed runtime in the same sense as it is in Lambda for Python, Java, or Node.js. Instead, the AWS Lambda Runtime for Rust is a crate (`lambda_runtime`) that supports writing Lambda functions in Rust and interfacing with AWS Lambda's execution environment. + +Use the following command to install the AWS Lambda Runtime for Rust: + + + cargo install cargo-lambda + +After you successfully install `cargo-lambda`, use the following command to initialize a new Rust Lambda function handler project: + @@ -28 +54 @@ The Lambda function _handler_ is the method in your function code that processes -Write your Lambda function code as a Rust executable. Implement the handler function code and a main function and include the following: + cargo lambda new example-rust @@ -30 +56 @@ Write your Lambda function code as a Rust executable. Implement the handler func - * The [lambda_runtime](https://crates.io/crates/lambda_runtime) crate from crates.io, which implements the Lambda programming model for Rust. +When you run this command, the command line interface (CLI) asks you a couple of questions about your Lambda function: @@ -32 +58 @@ Write your Lambda function code as a Rust executable. Implement the handler func - * Include [Tokio](https://crates.io/crates/tokio) in your dependencies. The [Rust runtime client for Lambda](https://github.com/awslabs/aws-lambda-rust-runtime) uses Tokio to handle asynchronous calls. + * **HTTP function** – If you intend to invoke your function via [API Gateway](./services-apigateway.html) or a [function URL](./urls-configuration.html), answer **Yes**. Otherwise, answer **No**. In the example code on this page, we invoke our function with a custom JSON event, so we answer **No**. @@ -33,0 +60 @@ Write your Lambda function code as a Rust executable. Implement the handler func + * **Event type** – If you intend to use a predefined event shape to invoke your function, select the correct expected event type. Otherwise, leave this option blank. In the example code on this page, we invoke our function with a custom JSON event, so we leave this option blank. @@ -37 +63,0 @@ Write your Lambda function code as a Rust executable. Implement the handler func -###### Example — Rust handler that processes JSON events @@ -39 +65 @@ Write your Lambda function code as a Rust executable. Implement the handler func -The following example uses the [serde_json](https://crates.io/crates/serde_json) crate to process basic JSON events: +After the command runs successfully, enter the main directory of your project: @@ -42,2 +68 @@ The following example uses the [serde_json](https://crates.io/crates/serde_json) - use lambda_runtime::{service_fn, LambdaEvent, Error}; - use serde_json::{json, Value}; + cd example-rust @@ -45 +70,23 @@ The following example uses the [serde_json](https://crates.io/crates/serde_json) - async fn handler(event: LambdaEvent<Value>) -> Result<Value, Error> { +This command generates a `generic_handler.rs` file and a `main.rs` file in the `src` directory. The `generic_handler.rs` can be used to customize a generic event handler. The `main.rs` file contains your main application logic. The `Cargo.toml` file contains metadata about your package and lists its external dependencies. + +## Example Rust Lambda function code + +The following example Rust Lambda function code takes in information about an order, produces a text file receipt, and puts this file in an Amazon S3 bucket. + +###### Example `main.rs` Lambda function + + + use aws_sdk_s3::{Client, primitives::ByteStream}; + use lambda_runtime::{run, service_fn, Error, LambdaEvent}; + use serde::{Deserialize, Serialize}; + use serde_json::Value; + use std::env; + + #[derive(Deserialize, Serialize)] + struct Order { + order_id: String, + amount: f64, + item: String, + } + + async fn function_handler(event: LambdaEvent<Value>) -> Result<String, Error> { @@ -47,2 +94,37 @@ The following example uses the [serde_json](https://crates.io/crates/serde_json) - let first_name = payload["firstName"].as_str().unwrap_or("world"); - Ok(json!({ "message": format!("Hello, {first_name}!") })) + + // Deserialize the incoming event into Order struct + let order: Order = serde_json::from_value(payload)?; + + let bucket_name = env::var("RECEIPT_BUCKET") + .map_err(|_| "RECEIPT_BUCKET environment variable is not set")?; + + let receipt_content = format!( + "OrderID: {}\nAmount: ${:.2}\nItem: {}", + order.order_id, order.amount, order.item + ); + let key = format!("receipts/{}.txt", order.order_id); + + let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let s3_client = Client::new(&config); + + upload_receipt_to_s3(&s3_client, &bucket_name, &key, &receipt_content).await?; + + Ok("Success".to_string()) + } + + async fn upload_receipt_to_s3( + client: &Client, + bucket_name: &str, + key: &str, + content: &str, + ) -> Result<(), Error> { + client + .put_object() + .bucket(bucket_name) + .key(key) + .body(ByteStream::from(content.as_bytes().to_vec())) // Fixed conversion + .content_type("text/plain") + .send() + .await?; + + Ok(()) @@ -53 +135 @@ The following example uses the [serde_json](https://crates.io/crates/serde_json) - lambda_runtime::run(service_fn(handler)).await + run(service_fn(function_handler)).await @@ -57 +139 @@ The following example uses the [serde_json](https://crates.io/crates/serde_json) -Note the following: +This `main.rs` file contains the following sections of code: @@ -59 +141 @@ Note the following: - * `use`: Imports the libraries that your Lambda function requires. + * `use` statements: Use these to import Rust crates and methods that your Lambda function requires. @@ -61 +143 @@ Note the following: - * `async fn main`: The entry point that runs the Lambda function code. The Rust runtime client uses [Tokio](https://tokio.rs/) as an async runtime, so you must annotate the main function with `#[tokio::main]`. + * `#[derive(Deserialize, Serialize)]`: Define the shape of the expected input event in this Rust struct. @@ -63 +145 @@ Note the following: - * `async fn handler(event: LambdaEvent<Value>) -> Result<Value,``Error``>`: This is the Lambda handler signature. It includes the code that runs when the function is invoked. + * `async fn function_handler(event: LambdaEvent<Value>) -> Result<String, Error>`: This is the **main handler method** , which contains your main application logic. @@ -65 +147 @@ Note the following: - * `LambdaEvent<Value>`: This is a generic type that describes the event received by the Lambda runtime as well as the [Lambda function context](./rust-context.html). + * `async fn upload_receipt_to_s3 (...)`: This is a helper method that's referenced by the main `function_handler` method. @@ -67 +149 @@ Note the following: - * `Result<Value, Error>`: The function returns a `Result`type. If the function is successful, the result is a JSON value. If the function is not successful, the result is an error. + * `#[tokio::main]`: This is a macro that marks the entry point of a Rust program. It also sets up a [Tokio runtime](https://docs.rs/tokio/latest/tokio/runtime/index.html), which allows your `main()` method to use `async`/`await` and run asynchronously. @@ -68,0 +151 @@ Note the following: + * `async fn main() -> Result<(), Error>`: The `main()` function is the entry point of your code. Within it, we specify `function_handler` as the main handler method. @@ -72 +154,0 @@ Note the following: -## Using shared state @@ -74 +156 @@ Note the following: -You can declare shared variables that are independent of your Lambda function's handler code. These variables can help you load state information during the [Init phase](./lambda-runtime-environment.html#runtimes-lifecycle-ib), before your function receives any events. +The following `Cargo.toml` file accompanies this function. @@ -76 +157,0 @@ You can declare shared variables that are independent of your Lambda function's -###### Example — Share Amazon S3 client across function instances @@ -78 +159,4 @@ You can declare shared variables that are independent of your Lambda function's -Note the following: + [package] + name = "example-rust" + version = "0.1.0" + edition = "2024" @@ -80 +164,7 @@ Note the following: - * `use aws_sdk_s3::Client`: This example requires you to add `aws-sdk-s3 = "0.26.0"` to the list of dependencies in your `Cargo.toml` file. + [dependencies] + aws-config = "1.5.18" + aws-sdk-s3 = "1.78.0" + lambda_runtime = "0.13.0" + serde = { version = "1", features = ["derive"] } + serde_json = "1" + tokio = { version = "1", features = ["full"] } @@ -82 +172 @@ Note the following: - * `aws_config::from_env`: This example requires you to add `aws-config = "0.55.1"` to the list of dependencies in your `Cargo.toml` file. +For this function to work properly, its [ execution role](./lambda-intro-execution-role.html) must allow the `s3:PutObject` action. Also, ensure that you define the `RECEIPT_BUCKET` environment variable. After a successful invocation, the Amazon S3 bucket should contain a receipt file. @@ -83,0 +174 @@ Note the following: +## Valid class definitions for Rust handlers @@ -84,0 +176 @@ Note the following: +In most cases, Lambda handler signatures that you define in Rust will have the following format: @@ -86,0 +179 @@ Note the following: + async fn function_handler(event: LambdaEvent<T>) -> Result<U, Error> @@ -88,3 +181,42 @@ Note the following: - use aws_sdk_s3::Client; - use lambda_runtime::{service_fn, Error, LambdaEvent}; - use serde::{Deserialize, Serialize}; +For this handler: + + * The name of this handler is `function_handler`. + + * The singular input to the handler is event, and is of type `LambdaEvent<T>`. + + * `LambdaEvent` is a wrapper that comes from the `lambda_runtime` crate. Using this wrapper gives you access to the context object, which includes Lambda-specific metadata such as the request ID of the invocation. + + * `T` is the deserialized event type. For example, this can be `serde_json::Value`, which allows the handler to take in any generic JSON input. Alternatively, this can be a type like `ApiGatewayProxyRequest` if your function expects a specific, pre-defined input type. + + * The return type of the handler is `Result<U, Error>`. + + * `U` is the deserialized output type. `U` must implement the `serde::Serialize` traint so Lambda can convert the return value to JSON. For example, `U` can be a simple type like `String`, `serde_json::Value`, or a custom struct as long as it implements `Serialize`. When your code reaches an Ok(U) statement, this indicates successful execution, and your function returns a value of type `U`. +