diff --git a/.github/workflows/publish-ec2-artifact.yml b/.github/workflows/publish-ec2-artifact.yml new file mode 100644 index 0000000..1125b19 --- /dev/null +++ b/.github/workflows/publish-ec2-artifact.yml @@ -0,0 +1,44 @@ +name: Publish EC2 entrypoint artifact + +on: + push: + tags: "v[0-9]+.[0-9]+.[0-9]+" + branches: + - "main" + - "develop/*" + paths: + - 'cmd/ec2_secrets/**' + - 'internal/**' + - 'pkg/**' + workflow_dispatch: + +permissions: + contents: write + +jobs: + publish-ec2-artifact: + runs-on: ubuntu-latest + + steps: + - name: Checkout + id: checkout + uses: actions/checkout@v3 + + - name: Go build + id: build + run: + make ec2-secrets-amd64 + + # - name: Set Release Tag + # id: tag + # run: | + # TAG=${GITHUB_REF##*/} + # echo $TAG + # echo "TAG=$TAG" >> $GITHUB_ENV + + - name: Release + id: release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + files: ec2/ec2-secrets \ No newline at end of file diff --git a/Makefile b/Makefile index 2136b7e..c84834f 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,6 @@ zip-amd64: build-amd64 zip -r aws-lambda-secrets-amd64.zip extensions/ @echo "Extension amd64 zip archive created" - fetch-secrets-arm64: GOOS=$(GOOS) GOARCH=arm64 go build \ -o extensions/fetch-secrets \ @@ -38,7 +37,12 @@ zip-arm64: build-arm64 release: zip-amd64 zip-arm64 clean +ec2-secrets-amd64: + GOOS=$(GOOS) GOARCH=amd64 go build \ + -o ec2/ec2-secrets \ + cmd/ec2-secrets/main.go + clean: - -rm -rf extensions + -rm -rf extensions ec2 .PHONY: build zip clean mod \ No newline at end of file diff --git a/cmd/ec2-secrets/main.go b/cmd/ec2-secrets/main.go new file mode 100644 index 0000000..a821de6 --- /dev/null +++ b/cmd/ec2-secrets/main.go @@ -0,0 +1,115 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "syscall" + + "github.com/joho/godotenv" + "github.com/skroutz/aws-lambda-secrets/internal/smsecrets" + "github.com/skroutz/aws-lambda-secrets/internal/utils" +) + +// Constants for default values if none are supplied +const DEFAULT_TIMEOUT = 5000 +const DEFAULT_REGION = "eu-central-1" +const DEFAULT_SECRETS_FILE = "secrets.yaml" +const DEFAULT_OUTPUT_FILE = "/tmp/lambda-secrets.env" +const DEFAULT_ENTRYPOINT_ENV_VAR = "ENTRYPOINT" + +var ( + timeout int + region string + secretsFile string + outputFileName string + entrypointEnvVar string + entrypointArray []string + + sm *smsecrets.SecretsManager +) + +func getCommandParams() { + // Setup command line args + flag.IntVar(&timeout, "t", utils.EnvOrInt("SECRETS_TIMEOUT", DEFAULT_TIMEOUT), "The amount of time to wait for any API call") + flag.StringVar(®ion, "r", utils.EnvOrString("SECRETS_AWS_REGION", DEFAULT_REGION), "The Amazon Region to use") + flag.StringVar(&secretsFile, "f", utils.EnvOrString("SECRETS_FILE", DEFAULT_SECRETS_FILE), + "The YAML file containing SecretsManager ARNs and Env Var names") + flag.StringVar(&outputFileName, "o", utils.EnvOrString("SECRETS_OUTPUT_FILE", DEFAULT_OUTPUT_FILE), + "The file that will be populated with SecretsManager secrets as Env Vars") + flag.StringVar(&entrypointEnvVar, "e", utils.EnvOrString("ENTRYPOINT", DEFAULT_ENTRYPOINT_ENV_VAR), + "The name of the Env Var storing the application entrypoint (Default: ENTRYPOINT)") + + // Parse all of the command line args into the specified vars with the defaults + flag.Parse() + + if flag.NArg() != 0 { + log.Printf("[*] Positional Argument treated as entrypoint: %s", flag.Args()) + entrypointArray = flag.Args() + } else if os.Getenv(entrypointEnvVar) != "" { + log.Printf("[*] Environment Variable '%s' is treated as entrypoint: %s", DEFAULT_ENTRYPOINT_ENV_VAR, os.Getenv(DEFAULT_ENTRYPOINT_ENV_VAR)) + } else { + log.Println("[!] No entrypoint found") + } +} + +func ExecuteEntrypoint() (string, error) { + err := godotenv.Load(outputFileName) + if err != nil { + log.Printf("[-] Error loading EnvVars from '%s' file. %s", outputFileName, err.Error()) + return "", err + } + + cmd := []byte{} + if entrypointArray == nil { + entrypoint := os.Getenv(entrypointEnvVar) + log.Printf("[+] Passing execution to entrypoint var '%s'\n\n", entrypoint) + err = syscall.Exec(entrypoint, nil, os.Environ()) + } else { + log.Printf("[+] Passing execution to positional entrypoint '%s'\n\n", entrypointArray) + err = syscall.Exec(entrypointArray[0], entrypointArray, os.Environ()) + } + if err != nil { + log.Printf("[-] Error running the entrypoint. '%s'", err) + return "", err + } + + fmt.Println(string(cmd)) + + log.Printf("[+] Execution finished") + return string(cmd), nil +} + +func LoadLambdaSecrets() (string, error) { + + // Check if output file exists + // If it does load it, pass execution and exit + log.Printf("[*] Looking for Dotenv file '%s'", outputFileName) + if stat, err := os.Stat(outputFileName); err == nil { + if stat.Size() != 0 { + log.Printf("Dotenv file '%s' found!", outputFileName) + } + } else { + log.Printf("[!] Dotenv file '%s' NOT found!", outputFileName) + log.Println("[*] Loading Secrets from AWS SecretsManager") + sm = smsecrets.NewSecretsManager(region, timeout) + secretArns := smsecrets.GetSecretArns(secretsFile) + sm.FetchSecrets(secretArns["secrets"]) + smsecrets.WriteEnvFile(outputFileName) + } + + // Now that the secrets are hopefully set + output, err := ExecuteEntrypoint() + + return output, err + +} + +func main() { + + getCommandParams() + + LoadLambdaSecrets() + +}