Skip to content

Commit

Permalink
Initial code
Browse files Browse the repository at this point in the history
  • Loading branch information
KyleKotowick committed Aug 10, 2021
1 parent ba44089 commit bffec85
Show file tree
Hide file tree
Showing 8 changed files with 331 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ override.tf.json

# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*

# Ignore the output archives
archives/
159 changes: 159 additions & 0 deletions LICENSE.md

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# terraform-aws-lambda-shell
Runs an arbitrary shell command in a Lambda function.
# Terraform Lambda Shell

This module creates a Lambda function that can run arbitrary shell commands. It is intended to be used with the [Invicton-Labs/lambda-shell-data/aws](https://registry.terraform.io/modules/Invicton-Labs/lambda-shell-data/aws/latest) and [Invicton-Labs/lambda-shell-resource/aws](https://registry.terraform.io/modules/Invicton-Labs/lambda-shell-resource/aws/latest) modules.
47 changes: 47 additions & 0 deletions lambda/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import subprocess
import uuid
import os

import logging

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)


def lambda_handler(event, context):
logger.debug("Input event: {}".format(event))

# If a special flag is set, skip the execution
if '__IL_TF_LS_SKIP_RUN' in event:
return {}

cmd = event['interpreter']
if event['command'] is not None:
# Create a unique file for the script to be temporarily stored in
scriptpath = "/tmp/botoform-{}".format(uuid.uuid1())
f = open(scriptpath, "x")
# Write the script to the file
f.write(event['command'])
f.close()
cmd.append(scriptpath)

# Run the command as a subprocess
logger.info("Running command: {}".format(cmd))
result = subprocess.run(cmd, shell=False, capture_output=True)

logger.debug("Result: {}".format(result))

if event['command'] is not None:
os.remove(scriptpath)

stdout = result.stdout.decode('utf-8')
stderr = result.stderr.decode('utf-8')
if result.returncode != 0 and event['fail_on_error']:
raise subprocess.SubprocessError(
"Command returned non-zero exit code ({}) with stdout '{}' and stderr '{}'".format(result.returncode, stdout, stderr))

return {
'exitstatus': result.returncode,
'stdout': stdout,
'stderr': stderr
}
37 changes: 37 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
resource "random_id" "lambda" {
byte_length = 14
}

locals {
// If no role/policy info is given, try using the current caller's role
lambda_role = var.lambda_role_arn == null && length(var.lambda_role_policies_json) == 0 && length(var.lambda_role_policy_arns) == 0 ? local.caller_role_arn : var.lambda_role_arn
}

// Create the Lambda that will execute the shell commands
module "shell_lambda" {
depends_on = [
module.assert_role_present.checked,
module.assert_single_body.checked
]
source = "Invicton-Labs/lambda-set/aws"
version = "0.4.1"
edge = false
source_directory = "${path.module}/lambda"
archive_output_directory = "${path.module}/archives/"
lambda_config = {
function_name = "invicton-labs-aws-lambda-shell-${random_id.lambda.hex}"
handler = "main.lambda_handler"
runtime = "python3.8"
timeout = var.lambda_timeout
memory_size = var.lambda_memory_size
role = local.lambda_role
tags = {
"ModuleAuthor" = "InvictonLabs"
"ModuleUrl" = "https://registry.terraform.io/modules/Invicton-Labs/lambda-shell/aws"
}
}
role_policies = var.lambda_role_policies_json
role_policy_arns = var.lambda_role_policy_arns
logs_lambda_subscriptions = var.lambda_logs_lambda_subscriptions
logs_non_lambda_subscriptions = var.lambda_logs_non_lambda_subscriptions
}
3 changes: 3 additions & 0 deletions outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "invicton_labs_lambda_shell_arn" {
value = module.shell_lambda.lambda.arn
}
75 changes: 75 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
variable "lambda_timeout" {
description = "The timeout (in seconds) for the Lambda function that is running the shell command."
type = number
default = 30
}

variable "lambda_memory_size" {
description = "The memory size (in MB) for the Lambda function that is running the shell command."
type = number
default = 128
}

variable "lambda_role_arn" {
description = "The ARN of the role to use for the Lambda that runs shell commands. If this value is provided, a new role will not be created. Conflicts with `lambda_role_policy_json`. If neither is provided, the module will attempt to use the role that the Terraform caller has assumed (if a role has been assumed)."
type = string
default = null
}

variable "lambda_role_policies_json" {
description = "A list of JSON-encoded policies to apply to a new role that will be created for the Lambda that runs shell commands. Conflicts with `lambda_role_arn`. If neither is provided, the module will attempt to use the role that the Terraform caller has assumed (if a role has been assumed)."
type = list(string)
default = []
}

variable "lambda_role_policy_arns" {
description = "A list of IAM policy ARNs to apply to a new role that will be created for the Lambda that runs shell commands. Conflicts with `lambda_role_arn`. If neither is provided, the module will attempt to use the role that the Terraform caller has assumed (if a role has been assumed)."
type = list(string)
default = []
}

variable "lambda_logs_lambda_subscriptions" {
description = "A list of configurations for Lambda subscriptions to the CloudWatch Logs Group for the Lambda function that runs shell commands. Each element should be a map with `destination_arn` (required), `name` (optional), `filter_pattern` (optional), and `distribution` (optional)."
type = list(object({
destination_arn = string
name = optional(string)
filter_pattern = optional(string)
distribution = optional(string)
}))
default = []
}

variable "lambda_logs_non_lambda_subscriptions" {
description = "A list of configurations for non-Lambda subscriptions to the CloudWatch Logs Group for the Lambda function that runs shell commands. Each element should be a map with `destination_arn` (required), `name` (optional), `filter_pattern` (optional), `role_arn` (optional), and `distribution` (optional)."
type = list(object({
destination_arn = string
name = optional(string)
filter_pattern = optional(string)
role_arn = optional(string)
distribution = optional(string)
}))
default = []
}

data "aws_caller_identity" "current" {}

data "aws_arn" "role" {
arn = data.aws_caller_identity.current.arn
}

locals {
caller_role_arn = substr(data.aws_arn.role.resource, 0, 5) == "role/" ? data.aws_caller_identity.current.arn : (substr(data.aws_arn.role.resource, 0, 13) == "assumed-role/" ? "arn:${data.aws_arn.role.partition}:iam::${data.aws_arn.role.account}:role/${split("/", data.aws_arn.role.resource)[1]}" : null)
}

module "assert_role_present" {
source = "Invicton-Labs/assertion/null"
version = "0.2.1"
condition = var.lambda_role_arn != null || length(var.lambda_role_policies_json) > 0 || length(var.lambda_role_policy_arns) > 0 || local.caller_role_arn != null
error_message = "One of the `lambda_role_arn`, `lambda_role_policies_json`, or `lambda_role_policy_arns` input parameters must be provided, or this module must be called from a Terraform configuration that has assumed a role."
}
module "assert_single_body" {
source = "Invicton-Labs/assertion/null"
version = "0.2.1"
condition = var.lambda_role_arn == null || (length(var.lambda_role_policies_json) == 0 && length(var.lambda_role_policy_arns) == 0)
error_message = "The `lambda_role_arn` cannot be provided when either the `lambda_role_policies_json` or `lambda_role_policy_arns` input parameter is provided."
}
4 changes: 4 additions & 0 deletions versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
terraform {
required_version = ">= 1.0.0"
experiments = [module_variable_optional_attrs]
}

0 comments on commit bffec85

Please sign in to comment.