forked from lobo/lambda-zig
add allow-principal option
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
|
* **profile**: AWS profile to use for credentials
|
||||||
* **role-name**: IAM role name for the function (default: lambda_basic_execution)
|
* **role-name**: IAM role name for the function (default: lambda_basic_execution)
|
||||||
* **env-file**: Path to environment variables file for the Lambda function
|
* **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
|
The Lambda function can be compiled for x86_64 or aarch64. The build system
|
||||||
automatically configures the Lambda architecture based on the target.
|
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.
|
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
|
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
|
/// - `-Dprofile=[string]`: AWS profile to use for credentials
|
||||||
/// - `-Drole-name=[string]`: IAM role name (default: "lambda_basic_execution")
|
/// - `-Drole-name=[string]`: IAM role name (default: "lambda_basic_execution")
|
||||||
/// - `-Dpayload=[string]`: JSON payload for invocation (default: "{}")
|
/// - `-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
|
/// ## Example
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,11 @@ pub fn configureBuild(
|
||||||
"env-file",
|
"env-file",
|
||||||
"Path to environment variables file (KEY=VALUE format)",
|
"Path to environment variables file (KEY=VALUE format)",
|
||||||
) orelse null;
|
) 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
|
// Determine architecture for Lambda
|
||||||
const target_arch = exe.root_module.resolved_target.?.result.cpu.arch;
|
const target_arch = exe.root_module.resolved_target.?.result.cpu.arch;
|
||||||
|
|
@ -94,6 +99,7 @@ pub fn configureBuild(
|
||||||
arch_str,
|
arch_str,
|
||||||
});
|
});
|
||||||
if (env_file) |ef| deploy_cmd.addArgs(&.{ "--env-file", ef });
|
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);
|
deploy_cmd.step.dependOn(&package_cmd.step);
|
||||||
|
|
||||||
const deploy_step = b.step("awslambda_deploy", "Deploy the Lambda function");
|
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_arn: ?[]const u8 = null;
|
||||||
var role_name: []const u8 = "lambda_basic_execution";
|
var role_name: []const u8 = "lambda_basic_execution";
|
||||||
var arch: ?[]const u8 = null;
|
var arch: ?[]const u8 = null;
|
||||||
|
var allow_principal: ?[]const u8 = null;
|
||||||
|
|
||||||
// Environment variables storage
|
// Environment variables storage
|
||||||
var env_vars = std.StringHashMap([]const u8).init(options.allocator);
|
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;
|
i += 1;
|
||||||
if (i >= args.len) return error.MissingEnvFile;
|
if (i >= args.len) return error.MissingEnvFile;
|
||||||
try loadEnvFile(args[i], &env_vars, options.allocator);
|
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")) {
|
} else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
|
||||||
printHelp(options.stdout);
|
printHelp(options.stdout);
|
||||||
try options.stdout.flush();
|
try options.stdout.flush();
|
||||||
|
|
@ -92,6 +97,7 @@ pub fn run(args: []const []const u8, options: RunOptions) !void {
|
||||||
.role_name = role_name,
|
.role_name = role_name,
|
||||||
.arch = arch,
|
.arch = arch,
|
||||||
.env_vars = if (env_vars.count() > 0) &env_vars else null,
|
.env_vars = if (env_vars.count() > 0) &env_vars else null,
|
||||||
|
.allow_principal = allow_principal,
|
||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,6 +185,8 @@ fn printHelp(writer: anytype) void {
|
||||||
\\ --arch <arch> Architecture: x86_64 or aarch64 (default: x86_64)
|
\\ --arch <arch> Architecture: x86_64 or aarch64 (default: x86_64)
|
||||||
\\ --env <KEY=VALUE> Set environment variable (can be repeated)
|
\\ --env <KEY=VALUE> Set environment variable (can be repeated)
|
||||||
\\ --env-file <path> Load environment variables from file (KEY=VALUE format)
|
\\ --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
|
\\ --help, -h Show this help message
|
||||||
\\
|
\\
|
||||||
\\Environment File Format:
|
\\Environment File Format:
|
||||||
|
|
@ -203,6 +211,7 @@ const DeployOptions = struct {
|
||||||
role_name: []const u8,
|
role_name: []const u8,
|
||||||
arch: ?[]const u8,
|
arch: ?[]const u8,
|
||||||
env_vars: ?*const std.StringHashMap([]const u8),
|
env_vars: ?*const std.StringHashMap([]const u8),
|
||||||
|
allow_principal: ?[]const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
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);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -320,6 +334,11 @@ fn deployFunction(deploy_opts: DeployOptions, options: RunOptions) !void {
|
||||||
|
|
||||||
// Wait for function to be ready before returning
|
// Wait for function to be ready before returning
|
||||||
try waitForFunctionReady(deploy_opts.function_name, options);
|
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
|
/// 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;
|
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