add allow-principal option
All checks were successful
Lambda-Zig Build / build (push) Successful in 31s
All checks were successful
Lambda-Zig Build / build (push) Successful in 31s
This commit is contained in:
parent
0f75605cc4
commit
b420abb0a1
4 changed files with 120 additions and 0 deletions
30
README.md
30
README.md
|
|
@ -19,6 +19,7 @@ Build options:
|
|||
* **profile**: AWS profile to use for credentials
|
||||
* **role-name**: IAM role name for the function (default: lambda_basic_execution)
|
||||
* **env-file**: Path to environment variables file for the Lambda function
|
||||
* **allow-principal**: AWS service principal to grant invoke permission (e.g., alexa-appkit.amazon.com)
|
||||
|
||||
The Lambda function can be compiled for x86_64 or aarch64. The build system
|
||||
automatically configures the Lambda architecture based on the target.
|
||||
|
|
@ -73,6 +74,35 @@ API_KEY=secret123
|
|||
|
||||
Lines starting with `#` are treated as comments. Empty lines are ignored.
|
||||
|
||||
Service Permissions
|
||||
-------------------
|
||||
|
||||
Lambda functions can be configured to allow invocation by AWS service principals.
|
||||
This is required for services like Alexa Skills Kit, API Gateway, or S3 to trigger
|
||||
your Lambda function.
|
||||
|
||||
### Using the build system
|
||||
|
||||
Pass the `-Dallow-principal` option to grant invoke permission to a service:
|
||||
|
||||
```sh
|
||||
# Allow Alexa Skills Kit to invoke the function
|
||||
zig build awslambda_deploy -Dfunction-name=my-skill -Dallow-principal=alexa-appkit.amazon.com
|
||||
|
||||
# Allow API Gateway to invoke the function
|
||||
zig build awslambda_deploy -Dfunction-name=my-api -Dallow-principal=apigateway.amazonaws.com
|
||||
```
|
||||
|
||||
### Using the CLI directly
|
||||
|
||||
```sh
|
||||
./lambda-build deploy --function-name my-fn --zip-file function.zip \
|
||||
--allow-principal alexa-appkit.amazon.com
|
||||
```
|
||||
|
||||
The permission is idempotent - if it already exists, the deployment will continue
|
||||
successfully.
|
||||
|
||||
Using the Zig Package Manager
|
||||
-----------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -134,6 +134,9 @@ fn configureBuildInternal(b: *std.Build, exe: *std.Build.Step.Compile) !void {
|
|||
/// - `-Dprofile=[string]`: AWS profile to use for credentials
|
||||
/// - `-Drole-name=[string]`: IAM role name (default: "lambda_basic_execution")
|
||||
/// - `-Dpayload=[string]`: JSON payload for invocation (default: "{}")
|
||||
/// - `-Denv-file=[string]`: Path to environment variables file (KEY=VALUE format)
|
||||
/// - `-Dallow-principal=[string]`: AWS service principal to grant invoke permission
|
||||
/// (e.g., "alexa-appkit.amazon.com" for Alexa Skills Kit)
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
|
|
|
|||
|
|
@ -39,6 +39,11 @@ pub fn configureBuild(
|
|||
"env-file",
|
||||
"Path to environment variables file (KEY=VALUE format)",
|
||||
) orelse null;
|
||||
const allow_principal = b.option(
|
||||
[]const u8,
|
||||
"allow-principal",
|
||||
"AWS service principal to grant invoke permission (e.g., alexa-appkit.amazon.com)",
|
||||
) orelse null;
|
||||
|
||||
// Determine architecture for Lambda
|
||||
const target_arch = exe.root_module.resolved_target.?.result.cpu.arch;
|
||||
|
|
@ -94,6 +99,7 @@ pub fn configureBuild(
|
|||
arch_str,
|
||||
});
|
||||
if (env_file) |ef| deploy_cmd.addArgs(&.{ "--env-file", ef });
|
||||
if (allow_principal) |ap| deploy_cmd.addArgs(&.{ "--allow-principal", ap });
|
||||
deploy_cmd.step.dependOn(&package_cmd.step);
|
||||
|
||||
const deploy_step = b.step("awslambda_deploy", "Deploy the Lambda function");
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ pub fn run(args: []const []const u8, options: RunOptions) !void {
|
|||
var role_arn: ?[]const u8 = null;
|
||||
var role_name: []const u8 = "lambda_basic_execution";
|
||||
var arch: ?[]const u8 = null;
|
||||
var allow_principal: ?[]const u8 = null;
|
||||
|
||||
// Environment variables storage
|
||||
var env_vars = std.StringHashMap([]const u8).init(options.allocator);
|
||||
|
|
@ -60,6 +61,10 @@ pub fn run(args: []const []const u8, options: RunOptions) !void {
|
|||
i += 1;
|
||||
if (i >= args.len) return error.MissingEnvFile;
|
||||
try loadEnvFile(args[i], &env_vars, options.allocator);
|
||||
} else if (std.mem.eql(u8, arg, "--allow-principal")) {
|
||||
i += 1;
|
||||
if (i >= args.len) return error.MissingAllowPrincipal;
|
||||
allow_principal = args[i];
|
||||
} else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
|
||||
printHelp(options.stdout);
|
||||
try options.stdout.flush();
|
||||
|
|
@ -92,6 +97,7 @@ pub fn run(args: []const []const u8, options: RunOptions) !void {
|
|||
.role_name = role_name,
|
||||
.arch = arch,
|
||||
.env_vars = if (env_vars.count() > 0) &env_vars else null,
|
||||
.allow_principal = allow_principal,
|
||||
}, options);
|
||||
}
|
||||
|
||||
|
|
@ -179,6 +185,8 @@ fn printHelp(writer: anytype) void {
|
|||
\\ --arch <arch> Architecture: x86_64 or aarch64 (default: x86_64)
|
||||
\\ --env <KEY=VALUE> Set environment variable (can be repeated)
|
||||
\\ --env-file <path> Load environment variables from file (KEY=VALUE format)
|
||||
\\ --allow-principal <p> Grant invoke permission to AWS service principal
|
||||
\\ (e.g., alexa-appkit.amazon.com)
|
||||
\\ --help, -h Show this help message
|
||||
\\
|
||||
\\Environment File Format:
|
||||
|
|
@ -203,6 +211,7 @@ const DeployOptions = struct {
|
|||
role_name: []const u8,
|
||||
arch: ?[]const u8,
|
||||
env_vars: ?*const std.StringHashMap([]const u8),
|
||||
allow_principal: ?[]const u8,
|
||||
};
|
||||
|
||||
fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
||||
|
|
@ -304,6 +313,11 @@ fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
|||
try updateFunctionConfiguration(deploy_opts.function_name, vars, options);
|
||||
}
|
||||
|
||||
// Add invoke permission if requested
|
||||
if (deploy_opts.allow_principal) |principal| {
|
||||
try addPermission(deploy_opts.function_name, principal, options);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -320,6 +334,11 @@ fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
|||
|
||||
// Wait for function to be ready before returning
|
||||
try waitForFunctionReady(deploy_opts.function_name, options);
|
||||
|
||||
// Add invoke permission if requested
|
||||
if (deploy_opts.allow_principal) |principal| {
|
||||
try addPermission(deploy_opts.function_name, principal, options);
|
||||
}
|
||||
}
|
||||
|
||||
/// Build environment variables in the format expected by AWS Lambda API
|
||||
|
|
@ -406,3 +425,65 @@ fn waitForFunctionReady(function_name: []const u8, options: RunOptions) !void {
|
|||
|
||||
return error.FunctionNotReady;
|
||||
}
|
||||
|
||||
/// Add invoke permission for a service principal
|
||||
fn addPermission(
|
||||
function_name: []const u8,
|
||||
principal: []const u8,
|
||||
options: RunOptions,
|
||||
) !void {
|
||||
const services = aws.Services(.{.lambda}){};
|
||||
|
||||
// Generate statement ID from principal: "alexa-appkit.amazon.com" -> "allow-alexa-appkit-amazon-com"
|
||||
var statement_id_buf: [128]u8 = undefined;
|
||||
var statement_id_len: usize = 0;
|
||||
|
||||
// Add "allow-" prefix
|
||||
const prefix = "allow-";
|
||||
@memcpy(statement_id_buf[0..prefix.len], prefix);
|
||||
statement_id_len = prefix.len;
|
||||
|
||||
// Sanitize principal: replace dots with dashes
|
||||
for (principal) |c| {
|
||||
if (statement_id_len >= statement_id_buf.len - 1) break;
|
||||
statement_id_buf[statement_id_len] = if (c == '.') '-' else c;
|
||||
statement_id_len += 1;
|
||||
}
|
||||
|
||||
const statement_id = statement_id_buf[0..statement_id_len];
|
||||
|
||||
std.log.info("Adding invoke permission for principal: {s}", .{principal});
|
||||
|
||||
var diagnostics = aws.Diagnostics{
|
||||
.http_code = undefined,
|
||||
.response_body = undefined,
|
||||
.allocator = options.allocator,
|
||||
};
|
||||
|
||||
var add_perm_options = options.aws_options;
|
||||
add_perm_options.diagnostics = &diagnostics;
|
||||
|
||||
const result = aws.Request(services.lambda.add_permission).call(.{
|
||||
.function_name = function_name,
|
||||
.statement_id = statement_id,
|
||||
.action = "lambda:InvokeFunction",
|
||||
.principal = principal,
|
||||
}, add_perm_options) catch |err| {
|
||||
defer diagnostics.deinit();
|
||||
|
||||
// 409 Conflict means permission already exists - that's fine
|
||||
if (diagnostics.http_code == 409) {
|
||||
std.log.info("Permission already exists for: {s}", .{principal});
|
||||
try options.stdout.print("Permission already exists for: {s}\n", .{principal});
|
||||
try options.stdout.flush();
|
||||
return;
|
||||
}
|
||||
|
||||
std.log.err("AddPermission failed: {} (HTTP {})", .{ err, diagnostics.http_code });
|
||||
return error.AddPermissionFailed;
|
||||
};
|
||||
defer result.deinit();
|
||||
|
||||
try options.stdout.print("Added invoke permission for: {s}\n", .{principal});
|
||||
try options.stdout.flush();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue