diff --git a/.github/workflows/terrafir.yaml b/.github/workflows/terrafir.yaml new file mode 100644 index 0000000..f4f6bfe --- /dev/null +++ b/.github/workflows/terrafir.yaml @@ -0,0 +1,81 @@ +name: Terrafir Test Action + +on: + push: + branches: + - master + workflow_dispatch: + inputs: + target: + description: "Test github action" + required: false + default: "run_terrafir_github_action" + type: choice + options: + - run_terrafir_github_action + cloud_provider: + description: "Cloud provider to use" + required: true + default: "aws" + type: choice + options: + - aws + - gcp + - azure + +env: + TF_LOG: INFO + CLOUD_PROVIDER: aws + REGION: us-east-2 + +jobs: + run_terrafir_github_action: + if: ${{ github.event.trigger != 'workflow_dispatch' || inputs.target == 'all' || inputs.target == 'run_terrafir_github_action' }} + runs-on: ubuntu-latest + name: Run Terrafir GitHub Action + defaults: + run: + working-directory: . + steps: + - name: Checkout Code + uses: actions/checkout@v2 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v1 + with: + terraform_version: 1.5.7 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + if: env.CLOUD_PROVIDER == 'aws' + with: + aws-access-key-id: ${{ secrets.ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.ACCESS_KEY }} + aws-region: ${{ env.REGION }} + + - name: Configure GCP Credentials + uses: google-github-actions/setup-gcloud@v1.1.1 + if: env.CLOUD_PROVIDER == 'gcp' + with: + project_id: ${{ secrets.GCP_PROJECT_ID }} + service_account_key: ${{ secrets.GCP_SA_KEY }} + export_default_credentials: true + + - name: Configure Azure Credentials + uses: azure/login@v1 + if: env.CLOUD_PROVIDER == 'azure' + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: Save plan to Runner workspace + id: plan + run: | + terraform plan --out tfplan.binary + terraform show -json tfplan.binary > input.json + + - name: Run Terrafir + uses: sachasmart/terrafir-github-action@v1 + with: + path: input.json + email: ${{ secrets.TERRAFIR_EMAIL }} + apiKey: ${{ secrets.TERRAFIR_API_KEY }} diff --git a/Dockerfile b/Dockerfile index 592fab8..1e22562 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,23 @@ -FROM golang:1.21.1-alpine3.14 AS base - +FROM scratch as scratch WORKDIR /app -COPY go.mod go.sum ./ COPY ./common ./common COPY ./types ./types +COPY ./input.json ./input.json + +FROM golang:1.21.1-alpine3.18 as base + +WORKDIR /app + +COPY go.mod go.sum ./ COPY main.go ./ RUN go mod download +COPY --from=scratch /app/input.json ./ +COPY --from=scratch /app/common ./common +COPY --from=scratch /app/types ./types + + +ENTRYPOINT [ "go", "run", "main.go" ] diff --git a/README.md b/README.md index dfef7a0..c3954e9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,14 @@ -# terrafir-github-action +# Terrafir Github Action +## 👋 Overview +This Github Action is used to assess Terraform plans using [Terrafir](https://www.terrafir.com). It is intended to be used in a CI/CD pipeline to assess Terraform plans before they are applied. You can see the output of the plan in the Terrafir dashboard or the Github Action output. +## 🚀 Usage +### Inputs +| Name | Description | Required | +| --- | --- | --- | +| apiKey | The API key used to authenticate with Terrafir | true | +| email | The email address of the user who owns the API key | true | +| __WIP: verbose__ | Whether or not to print the assessment to the Github Action output | false | ### Flow @@ -9,11 +18,11 @@ title: Terrafir Github Action --- flowchart TD - A["`__Github Action__ + A["Github Action Inputs: - - Terrafir API Key - - Plan to Assess - `"]--Spins up Docker Container - Dockerfile-->B[Container] + - Terrafir API Key (apiKey) + - Email of Terrafir Account (email) + "]--Spins up Docker Container - Dockerfile-->B[Container] B --Make Post Request to Terrafir API-->C[Terrafir API] C --Authorizes Request-->D[AuthService] D -.Allow Plan Assessment.->E @@ -22,3 +31,86 @@ flowchart TD C --Processes Assessment and Returns Result to Container-->B B --Receives Assessment and Creates Github Comment Output-->A ``` + +### Sample CI/CD Pipeline Using Terrafir Github Action +```yaml +name: Terrafir Test Action + +on: + push: + branches: + - master + workflow_dispatch: + inputs: + target: + description: "Test github action" + required: false + default: "run_terrafir_github_action" + type: choice + options: + - run_terrafir_github_action + cloud_provider: + description: "Cloud provider to use" + required: true + default: "aws" + type: choice + options: + - aws + - gcp + - azure + +env: + CLOUD_PROVIDER: aws + REGION: us-east-2 + +jobs: + run_terrafir_github_action: + if: ${{ github.event.trigger != 'workflow_dispatch' || inputs.target == 'all' || inputs.target == 'run_terrafir_github_action' }} + runs-on: ubuntu-latest + name: Run Terrafir GitHub Action + defaults: + run: + working-directory: . + steps: + - name: Checkout Code + uses: actions/checkout@v2 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v1 + with: + terraform_version: 1.5.7 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + if: env.CLOUD_PROVIDER == 'aws' + with: + aws-access-key-id: ${{ secrets.ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.ACCESS_KEY }} + aws-region: ${{ env.REGION }} + + - name: Configure GCP Credentials + uses: google-github-actions/setup-gcloud@v1.1.1 + if: env.CLOUD_PROVIDER == 'gcp' + with: + project_id: ${{ secrets.GCP_PROJECT_ID }} + service_account_key: ${{ secrets.GCP_SA_KEY }} + export_default_credentials: true + + - name: Configure Azure Credentials + uses: azure/login@v1 + if: env.CLOUD_PROVIDER == 'azure' + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: Save plan to Runner workspace + id: plan + run: | + terraform plan --out tfplan.binary + terraform show -json tfplan.binary > input.json # Currently needs to output to input.json + + - name: Run Terrafir + uses: sachasmart/terrafir-github-action@v1 + with: + email: ${{ secrets.TERRAFIR_EMAIL }} + apiKey: ${{ secrets.TERRAFIR_API_KEY }} +``` \ No newline at end of file diff --git a/action.yml b/action.yml index 5588f2e..bab8b1d 100644 --- a/action.yml +++ b/action.yml @@ -1,17 +1,15 @@ name: 'Terrafir Plan Assessment' -description: 'Assess the plan file for security vulnerabilities' +description: 'Use Terrafir to assess the Terraform plan file for security vulnerabilities' branding: icon: 'shield' color: 'blue' inputs: - api-key: + apiKey: description: 'API key for Terrafir' required: true email: description: 'Email for Terrafir' required: true - plan-file: - description: 'Path to the terraform plan file' outputs: time: description: 'The time plan was assessed' #TODO @@ -19,6 +17,5 @@ runs: using: 'docker' image: 'Dockerfile' args: - - ${{ inputs.api-key }} - - ${{ inputs.email }} - - ${{ github.workspace }}/${{ inputs.plan-file }} + - ${{ inputs.apiKey }} + - ${{ inputs.email }} \ No newline at end of file diff --git a/main.go b/main.go index bca032f..8898599 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,6 @@ package main import ( "bytes" "encoding/json" - "flag" "fmt" "io" "log" @@ -17,17 +16,25 @@ import ( ) var ( - apiKey = flag.String("apiKey", "", "API key Terrafir API.") - email = flag.String("email", "", "Email address to send the request to.") - inputFilePath = flag.String("input", "", "Input file path to the plan that will be assessed.") - verboseMode = flag.String("verbose", "", "Verbose mode") + apiKey = getEnv("INPUT_APIKEY", "") + email = getEnv("INPUT_EMAIL", "") + path = getEnv("INPUT_PATH", "") + verboseMode = getEnv("verboseMode", "") ) +func getEnv(key, defaultValue string) string { + fmt.Println(os.Environ()) + value := os.Getenv(key) + if value == "" { + fmt.Print("Using default value for ", key, ": ", defaultValue, "\n") + return defaultValue + } + return value +}+ + func main() { - flag.Parse() preRequestCheck() - checkEnvironmentVariables(*apiKey, *email, *inputFilePath) - sendRequest(*apiKey, *email, *inputFilePath) + sendRequest(apiKey, email) } func preRequestCheck() { @@ -43,15 +50,15 @@ func preRequestCheck() { color.Green(fmt.Sprintf("API is available %s", response.Status)) } -func sendRequest(apiKey string, email string, inputFilePath string) { - color.Green(fmt.Sprintf("Using input file: %s", inputFilePath)) +func sendRequest(apiKey string, email string) { + color.Green(fmt.Sprintf("Using input file: %s", "./input.json")) payload := &bytes.Buffer{} writer := multipart.NewWriter(payload) - file, errFile1 := os.Open(inputFilePath) + file, errFile1 := os.Open("./input.json") defer file.Close() part1, - errFile1 := writer.CreateFormFile("plan", filepath.Base(inputFilePath)) + errFile1 := writer.CreateFormFile("plan", filepath.Base("./input.json")) _, errFile1 = io.Copy(part1, file) if errFile1 != nil { fmt.Println(errFile1) @@ -99,15 +106,3 @@ func formattedBody(body []byte) { fmt.Println(formattedBody) } - -func checkEnvironmentVariables(apiKey string, email string, input string) { - if apiKey == "" { - log.Fatalf("API key not provided.") - } - if email == "" { - log.Fatalf("Email address not provided.") - } - if input == "" { - log.Fatalf("Input not provided.") - } -}