Cargo lambda build does not find OpenSSL development headers

878 views Asked by At

I'm trying to write a lambda function for AWS in Rust using cargo-lambda. The example function generated by cargo lambda new builds fine when I call cargo lambda build --release --arm64. However, when I try to build code I write by following the instructions and examples on https://github.com/awslabs/aws-lambda-rust-runtime & https://www.cargo-lambda.info/guide/getting-started.html, the build fails to find the headers for OpenSSL. I already installed OpenSSL with pacman -S openssl and ran pacman -S linux-headers for good measure, and tried running export OPENSSL_LIB_DIR=/usr/lib/ and export OPENSSL_INCLUDE_DIR=/usr/include/openssl/ as well as setting them in my Fish config file, but neither has worked. I can't get past:

cargo:warning=build/expando.c:1:10: fatal error: 'openssl/opensslv.h' file not found
cargo:warning=#include <openssl/opensslv.h>

I have visually verified the file exists in the directory but for some reason openssl-sys-0.9.87 fails to find it and panics. Has anyone else encountered this, or does anyone have any ideas I can try? I'm not sure if the problem is with my cargo-lambda setup, my OpenSSL setup, my code, or something else entirely.

This code was generated with cargo lambda new project_name and builds fine:

use lambda_runtime::{run, service_fn, Error, LambdaEvent};
use serde::{Deserialize, Serialize};

/// Main function generated by cargo-lambda
#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        // disable printing the name of the module in every log line.
        .with_target(false)
        // disabling time is handy because CloudWatch will add the ingestion time.
        .without_time()
        .init();

    lambda_runtime::run(service_fn(function_handler)).await
}

/// This is a made-up example.
#[derive(Deserialize)]
struct Request {
    command: String,
}

/// This is a made-up example of what a response structure may look like.
#[derive(Serialize)]
struct Response {
    req_id: String,
    msg: String,
}

/// This is the main body for the function.
/// Write your code inside it.
async fn function_handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
    // Extract some useful info from the request
    let command = event.payload.command;

    // Prepare the response
    let resp = Response {
        req_id: event.context.request_id,
        msg: format!("Command {}.", command),
    };

    // Return `Response` (it will be serialized to JSON automatically by the runtime)
    Ok(resp)
}

This is the code I'm trying to build but getting an error from openssl-sys-0.9.87 even though it was written in the same project and follows the examples from the online documentation:

use lambda_runtime::{run, service_fn, Error, LambdaEvent};
use reqwest::blocking::Client;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};

#[tokio::main]
async fn main() -> Result<(), lambda_runtime::Error> {
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        // disable printing the name of the module in every log line.
        .with_target(false)
        // disabling time is handy because CloudWatch will add the ingestion time.
        .without_time()
        .init();

    let query = service_fn(query_handler);
    run(query).await
}

async fn query_handler(event: LambdaEvent<Value>) -> Result<Value, Error> {
    let (event, _context) = event.into_parts();
    let symbol = event["symbol"].to_string();
    let message = query_price(symbol)?;
    Ok(json!({ "message": format!("{}", message) }))
}

#[derive(Deserialize, Debug)]
#[allow(non_snake_case)]
struct PriceQuote {
    pub s: String,
    pub symbol: Vec<String>,
    pub ask: Vec<f32>,
    pub askSize: Vec<u32>,
    pub bid: Vec<f32>,
    pub bidSize: Vec<u32>,
    pub mid: Vec<f32>,
    pub last: Vec<f32>,
    pub volume: Vec<u32>,
    pub updated: Vec<u32>,
}

fn query_price(symbol: String) -> Result<String, reqwest::Error> {
    //let symbol = "AAPL";

    let url = format!("https://api.marketdata.app/v1/stocks/quotes/{}", symbol);
    let client = Client::new();
    let response = client.get(url).send()?;
    let price_quote: PriceQuote = response.json()?;

    let symbol: &String = &price_quote.symbol[0];
    let last_price: &f32 = &price_quote.last[0];

    Ok(format!("Last price for {} is {}", symbol, last_price).to_string())
}

Why would one build but not the other? Are the dependencies I added messing with it? Do I have the wrong OpenSSL version? Did I make a mistake? What am I missing? Sorry if these are dumb questions, I'm new to AWS and my head is spinning.

2

There are 2 answers

1
J Stach On BEST ANSWER

Unsure of exactly what went wrong, but it seems the reqwest crate was the root of the trouble. I've never had this problem except when building via cargo-lambda so I imagine it has something to do with Zig?

Either way, it can be solved by adding openssl directly to Cargo.toml:

openssl = { version = "0.10.35", features = ["vendored"] }

This seems to provide the correct linkage for reqwest and allows cargo lambda to build. Haven't tested if reqwest still works but I see no reason it shouldn't.

Update: The function deploys and reqwest works fine

0
Gary On

AWS Lambda functions execute inside Amazon Linux 2 sandboxes, which include only bare minimum functionality to run Rust binaries and do not include OpenSSL. cargo lambda build does not build against what's available on a host (build) system, but against what will be available on a guest (runtime) Linux sandbox, hence the failure to find OpenSSL headers. Cargo Lambda docs mention:

`*-sys` libraries are not guaranteed to work unless they are completely linked to your binary

The Rust openssl crate can be switched to compile and link statically with openssl = { version = "0.10.35", features = ["vendored"] } in Cargo.toml (as already mentioned by j-stach). Analogously, the Rust reqwest crate, a HTTPS client dependent on OpenSSL, can be switched to compile and link statically with reqwest = { version = "0.11.23", features = ["native-tls-vendored"] } or reqwest = { version = "0.11.23", default-features = false, features = ["rustls"] }.

Finally, if you run into the beloved cargo lambda build issue "unsupported linker arg: --no-undefined-version", just downgrade your Rust to 1.69 with rustup default 1.69.