Initial commit of working lambda

This commit is contained in:
Gabriel Simmer 2023-05-21 16:48:29 +01:00
commit 19e2b85308
Signed by: arch
GPG key ID: C81B106D46C5B875
9 changed files with 2226 additions and 0 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/target
result*
.direnv/

1853
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

18
Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
[package]
name = "quick-start"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
aws-config = "0.49.0"
aws-sdk-s3 = "0.19.0"
lambda_runtime = "0.6.1"
lambda_http = "0.8"
serde = "1.0.136"
serde_json = "1.0.85"
tokio = { version = "1", features = ["macros"] }
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] }
url = "2.3"

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Gabriel Simmer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

19
README.md Normal file
View file

@ -0,0 +1,19 @@
webfinger lambda
===
small aws lambda that filters a static JSON file of webfinger data from an s3 bucket
and returns the one matching the query parameter `subject`.
## building
use the [`cargo-lambda`](https://www.cargo-lambda.info/) tool to build the zip file to upload
to aws
```shell
$ cargo lambda build --release --output-format zip
```
because it's a lambda there is currently no way to run this locally. this should
eventually be rectified.
you can then [point a custom domain](https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html) to the lambda.

159
flake.lock Normal file
View file

@ -0,0 +1,159 @@
{
"nodes": {
"crane": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"nixpkgs": [
"nixpkgs"
],
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1684468982,
"narHash": "sha256-EoC1N5sFdmjuAP3UOkyQujSOT6EdcXTnRw8hPjJkEgc=",
"owner": "ipetkov",
"repo": "crane",
"rev": "99de890b6ef4b4aab031582125b6056b792a4a30",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1684464849,
"narHash": "sha256-f8th/GWE9M2hePTMZc0YyFboigt9AG/ioEcyHcdFK2I=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b969a89c3e84a121c9b3af2e4ef277cd822b988a",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"crane": "crane",
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": [
"crane",
"flake-utils"
],
"nixpkgs": [
"crane",
"nixpkgs"
]
},
"locked": {
"lastModified": 1683080331,
"narHash": "sha256-nGDvJ1DAxZIwdn6ww8IFwzoHb2rqBP4wv/65Wt5vflk=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "d59c3fa0cba8336e115b376c2d9e91053aa59e56",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

53
flake.nix Normal file
View file

@ -0,0 +1,53 @@
{
description = "Build a cargo project without extra checks";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
crane = {
url = "github:ipetkov/crane";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, crane, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
};
craneLib = crane.lib.${system};
my-crate = craneLib.buildPackage {
src = craneLib.cleanCargoSource (craneLib.path ./.);
buildInputs = [
] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
pkgs.libiconv
];
};
in
{
checks = {
inherit my-crate;
};
packages.default = my-crate;
apps.default = flake-utils.lib.mkApp {
drv = my-crate;
};
devShells.default = pkgs.mkShell {
inputsFrom = builtins.attrValues self.checks.${system};
nativeBuildInputs = with pkgs; [
cargo
rustc
cargo-lambda
rustup
];
};
});
}

99
src/main.rs Normal file
View file

@ -0,0 +1,99 @@
use lambda_runtime::{Error};
use lambda_http::{service_fn, Body, Request, Response, IntoResponse};
use serde::{Deserialize, Serialize};
use url;
#[derive(Deserialize, Debug, Serialize)]
struct Webfinger {
subject: String,
links: Vec<Link>,
#[serde(skip_serializing_if = "Option::is_none")]
aliases: Option<Vec<String>>,
}
#[derive(Deserialize, Debug, Serialize)]
struct Link {
rel: String,
#[serde(skip_serializing_if = "Option::is_none")]
href: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
type_: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
template: Option<String>,
}
// Get object from S3 bucket.
async fn get_object(
s3_client: &aws_sdk_s3::Client,
bucket_name: &str,
object_key: &str,
request: Request,
) -> Result<impl IntoResponse, Error> {
// Get subject name from request URL parameter
let subject = request
.uri()
.query()
.and_then(|query_string| {
url::form_urlencoded::parse(query_string.as_bytes())
.find(|(key, _)| key == "resource")
.map(|(_, value)| value.into_owned())
})
.unwrap_or_else(|| "acct:".to_string());
let resp = s3_client
.get_object()
.bucket(bucket_name)
.key(object_key)
.send()
.await?;
let body = resp.body.collect().await?.into_bytes();
// Response to an array of webfinger objects
let webfingers: Vec<Webfinger> = serde_json::from_slice(&body).unwrap();
// Find the webfinger object with the subject
let webfinger = webfingers.iter().find(|&x| x.subject == subject);
Ok(match webfinger {
Some(webfinger) => {
let body = serde_json::to_string(&webfinger).unwrap();
Response::builder()
.status(200)
.header("Content-Type", "application/jrd+json")
.body(Body::from(body))
.expect("failed to render response")
}
None => {
let body = format!("No webfinger object found for subject: {}", subject);
Response::builder()
.status(404)
.body(Body::from(body))
.expect("failed to render response")
}
})
}
#[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();
let bucket_name = std::env::var("BUCKET_NAME")
.expect("A BUCKET_NAME must be set in this app's Lambda environment variables.");
// Initialize the client here to be able to reuse it across
// different invocations.
//
// No extra configuration is needed as long as your Lambda has
// the necessary permissions attached to its role.
let config = aws_config::load_from_env().await;
let s3_client = aws_sdk_s3::Client::new(&config);
lambda_http::run(service_fn(|request: Request| async {
// Get subject name from request URL parameter
get_object(&s3_client, &bucket_name, ".well-known/webfinger.json", request).await
})).await
}