From ec9b46099e2069d0abe42ecc2cbd69b705452928 Mon Sep 17 00:00:00 2001 From: Suhong Qin <51539171+sqin2019@users.noreply.github.com> Date: Tue, 29 Aug 2023 17:04:59 -0700 Subject: [PATCH] feat: added iam cleanup in workflow (#111) --- .github/workflows/cleanup.yml | 284 +++++++++++++++++++++++++++++ .github/workflows/tool_cleanup.yml | 178 ------------------ 2 files changed, 284 insertions(+), 178 deletions(-) create mode 100644 .github/workflows/cleanup.yml delete mode 100644 .github/workflows/tool_cleanup.yml diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml new file mode 100644 index 0000000..f0fab39 --- /dev/null +++ b/.github/workflows/cleanup.yml @@ -0,0 +1,284 @@ +# Copyright 2023 The Authors (see AUTHORS file) + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reusable workflow that handles AOD request cleanup. +name: 'aod-cleanup' + +on: + workflow_call: + inputs: + workload_identity_provider: + description: 'The full identifier of the Workload Identity Provider, including the project number, pool name, and provider name.' + type: 'string' + required: true + service_account: + description: 'Email address or unique identifier of the Google Cloud service account for which to generate credentials.' + type: 'string' + required: true + aod_cli_version: + description: 'The version of AOD CLI.' + type: 'string' + default: 'latest' + required: false + go_version: + description: 'The version of Golang.' + type: 'string' + default: '1.21' + required: false + +env: + IAM_ERROR_FILENAME: 'iam_error.txt' + IAM_OUT_FILENAME: 'iam_output.txt' + TOOL_ERROR_FILENAME: 'tool_error.txt' + TOOL_OUT_FILENAME: 'tool_output.txt' + +jobs: + # Check the current status of this pull request with respect to code review. + review_status: + runs-on: 'ubuntu-latest' + permissions: + pull-requests: 'read' + outputs: + REVIEW_DECISION: '${{ steps.get_review_decision.outputs.REVIEW_DECISION }}' + steps: + - id: 'get_review_decision' + env: + # Set the GH_TOKEN environment variable to use GitHub CLI in a GitHub Actions workflow. + # See ref: https://docs.github.com/en/actions/using-workflows/using-github-cli-in-workflows + GH_TOKEN: '${{ github.token }}' + run: | + repo=${{ github.repository }} + reviewDecision="$(gh api graphql -F owner=${{ github.repository_owner }} -F name=${repo##*/} -F pr_number=${{ github.event.pull_request.number }} -f query=' + query($name: String!, $owner: String!, $pr_number: Int!) { + repository(owner: $owner, name: $name) { + pullRequest(number: $pr_number) { + reviewDecision + } + } + } + ' --jq '.data.repository.pullRequest.reviewDecision')" + + echo REVIEW_DECISION=$reviewDecision >> $GITHUB_OUTPUT + + # Only run Tool request cleanup when the pull request is approved. + cleanup: + needs: 'review_status' + if: '${{ needs.review_status.outputs.REVIEW_DECISION == ''APPROVED'' }}' + runs-on: 'ubuntu-latest' + permissions: + contents: 'read' + id-token: 'write' + pull-requests: 'write' + name: 'Handle AOD Request Cleanup' + steps: + - name: 'Checkout Triggering Branch' + uses: 'actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab' # ratchet:actions/checkout@v3 + with: + ref: '${{ github.event.pull_request.head.ref }}' + - name: 'Setup Go' + uses: 'actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568' # ratchet:actions/setup-go@v3 + with: + go-version: '${{ inputs.go_version }}' + - name: 'Authenticate to Google Cloud' + uses: 'google-github-actions/auth@35b0e87d162680511bf346c299f71c9c5c379033' # ratchet:google-github-actions/auth@v1 + with: + workload_identity_provider: '${{ inputs.workload_identity_provider }}' + service_account: '${{ inputs.service_account }}' + token_format: 'access_token' + # Install gcloud, `setup-gcloud` automatically picks up authentication from `auth`. + - name: 'Set up Cloud SDK for tool request' + if: '${{ hashFiles(''tool.yaml'') != '''' }}' + uses: 'google-github-actions/setup-gcloud@e30db14379863a8c79331b04a9969f4c1e225e0b' # ratchet:google-github-actions/setup-gcloud@v1 + - name: 'Install AOD CLI' + run: 'go install github.com/abcxyz/access-on-demand/cmd/aod@${{ inputs.aod_cli_version }}' + - name: 'Handle tool cleanup' + if: '${{ hashFiles(''tool.yaml'') != '''' }}' + id: 'cleanup_tool' + env: + TOOL_FILE_PATH: '${{ github.workspace }}/tool.yaml' + run: | + touch ${{ runner.temp }}/${{ env.TOOL_ERROR_FILENAME }} + touch ${{ runner.temp }}/${{ env.TOOL_OUT_FILENAME }} + aod tool cleanup -path ${{ env.TOOL_FILE_PATH }} \ + 2> ${{ runner.temp }}/${{ env.TOOL_ERROR_FILENAME }} \ + > ${{ runner.temp }}/${{ env.TOOL_OUT_FILENAME }} + - name: 'Handle IAM cleanup' + if: '${{ hashFiles(''iam.yaml'') != '''' }}' + id: 'cleanup_iam' + env: + IAM_FILE_PATH: '${{ github.workspace }}/iam.yaml' + run: | + touch ${{ runner.temp }}/${{ env.IAM_ERROR_FILENAME }} + touch ${{ runner.temp }}/${{ env.IAM_OUT_FILENAME }} + aod iam cleanup -path ${{ env.IAM_FILE_PATH }} \ + 2> ${{ runner.temp }}/${{ env.IAM_ERROR_FILENAME }} \ + > ${{ runner.temp }}/${{ env.IAM_OUT_FILENAME }} + - name: 'Tool Request Success Cleanup Comment' + if: '${{ always() && steps.cleanup_tool.outcome == ''success'' }}' + uses: 'actions/github-script@98814c53be79b1d30f795b907e553d8679345975' # ratchet:actions/github-script@v6 + with: + github-token: '${{ github.token }}' + retries: '3' + script: |+ + const fs = require("fs"); + + const toolOutFilename = "${{ runner.temp }}/${{ env.TOOL_OUT_FILENAME }}"; + const req = fs.readFileSync(toolOutFilename, { encoding: "utf8" }); + + const body = `**\`Access on Demand\`** - 🟩 **\`Tool\`** request cleanup succeeded. + +
+ Details + Executed "cleanup" commands in the request below, or skipped if commands not found. + + \`\`\` + ${req} + \`\`\` +
`; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: "${{ github.event.pull_request.number }}", + body: body, + }); + - name: 'Tool Request Failure Cleanup Comment' + if: '${{ always() && steps.cleanup_tool.outcome == ''failure'' }}' + uses: 'actions/github-script@98814c53be79b1d30f795b907e553d8679345975' # ratchet:actions/github-script@v6 + with: + github-token: '${{ github.token }}' + retries: '3' + script: |+ + const fs = require("fs"); + + const toolOutFilename = "${{ runner.temp }}/${{ env.TOOL_OUT_FILENAME }}"; + const toolErrFilename = "${{ runner.temp }}/${{ env.TOOL_ERROR_FILENAME }}"; + + const req = fs.readFileSync(toolOutFilename, { encoding: "utf8" }); + const error = fs.readFileSync(toolErrFilename, { encoding: "utf8" }); + + const body = `**\`Access on Demand\`** - 🟥 **\`Tool\`** request cleanup failed. + +
+ Details + Failed to execute "cleanup" commands in the request below. + + \`\`\` + ${req} + \`\`\` + + Error: + \`\`\` + ${error} + \`\`\` +
`; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: "${{ github.event.pull_request.number }}", + body: body, + }); + - name: 'Tool Request Not Found Comment' + if: '${{ always() && hashFiles(''tool.yaml'') == '''' }}' + uses: 'actions/github-script@98814c53be79b1d30f795b907e553d8679345975' # ratchet:actions/github-script@v6 + with: + github-token: '${{ github.token }}' + retries: '3' + script: |+ + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: ${{ github.event.pull_request.number }}, + body: `**\`Access on Demand\`** - 🟦 **\`Tool\`** request not found, skip cleanup.`, + }); + - name: 'IAM Request Success Cleanup Comment' + if: '${{ always() && steps.cleanup_iam.outcome == ''success'' }}' + uses: 'actions/github-script@98814c53be79b1d30f795b907e553d8679345975' # ratchet:actions/github-script@v6 + with: + github-token: '${{ github.token }}' + retries: '3' + script: |+ + const fs = require("fs"); + + const iamOutFilename = "${{ runner.temp }}/${{ env.IAM_OUT_FILENAME }}"; + const req = fs.readFileSync(iamOutFilename, { encoding: "utf8" }); + + const body = `**\`Access on Demand\`** - 🟩 **\`IAM\`** request cleanup succeeded. + +
+ Details + Removed bindings in the request below. + + \`\`\` + ${req} + \`\`\` +
`; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: "${{ github.event.pull_request.number }}", + body: body, + }); + - name: 'IAM Request Failure Cleanup Comment' + if: '${{ always() && steps.cleanup_iam.outcome == ''failure'' }}' + uses: 'actions/github-script@98814c53be79b1d30f795b907e553d8679345975' # ratchet:actions/github-script@v6 + with: + github-token: '${{ github.token }}' + retries: '3' + script: |+ + const fs = require("fs"); + + const iamOutFilename = "${{ runner.temp }}/${{ env.IAM_OUT_FILENAME }}"; + const iamErrFilename = "${{ runner.temp }}/${{ env.IAM_ERROR_FILENAME }}"; + + const req = fs.readFileSync(iamOutFilename, { encoding: "utf8" }); + const error = fs.readFileSync(iamErrFilename, { encoding: "utf8" }); + + const body = `**\`Access on Demand\`** - 🟥 **\`IAM\`** request cleanup failed. + +
+ Details + Failed to cleanup IAM polices of the resources in the request below. + + \`\`\` + ${req} + \`\`\` + + Error: + \`\`\` + ${error} + \`\`\` +
`; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: "${{ github.event.pull_request.number }}", + body: body, + }); + - name: 'IAM Request Not Found Comment' + if: '${{ always() && hashFiles(''iam.yaml'') == '''' }}' + uses: 'actions/github-script@98814c53be79b1d30f795b907e553d8679345975' # ratchet:actions/github-script@v6 + with: + github-token: '${{ github.token }}' + retries: '3' + script: |+ + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: ${{ github.event.pull_request.number }}, + body: `**\`Access on Demand\`** - 🟦 **\`IAM\`** request not found, skip cleanup.`, + }); diff --git a/.github/workflows/tool_cleanup.yml b/.github/workflows/tool_cleanup.yml deleted file mode 100644 index 0c2597f..0000000 --- a/.github/workflows/tool_cleanup.yml +++ /dev/null @@ -1,178 +0,0 @@ -# Copyright 2023 The Authors (see AUTHORS file) - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reusable workflow that handles tool request cleanup. -name: 'aod-tool-cleanup' - -on: - workflow_call: - inputs: - workload_identity_provider: - description: 'The full identifier of the Workload Identity Provider, including the project number, pool name, and provider name.' - type: 'string' - required: true - service_account: - description: 'Email address or unique identifier of the Google Cloud service account for which to generate credentials.' - type: 'string' - required: true - aod_cli_version: - description: 'The version of AOD CLI.' - type: 'string' - default: 'latest' - required: false - go_version: - description: 'The version of Golang.' - type: 'string' - default: '1.21' - required: false - -env: - TOOL_ERROR_FILENAME: '/tmp/tool_error.txt' - TOOL_OUT_FILENAME: '/tmp/tool_output.txt' - -jobs: - # Check the current status of this pull request with respect to code review. - review_status: - runs-on: 'ubuntu-latest' - permissions: - pull-requests: 'read' - outputs: - REVIEW_DECISION: '${{ steps.get_review_decision.outputs.REVIEW_DECISION }}' - steps: - - id: 'get_review_decision' - env: - # Set the GH_TOKEN environment variable to use GitHub CLI in a GitHub Actions workflow. - # See ref: https://docs.github.com/en/actions/using-workflows/using-github-cli-in-workflows - GH_TOKEN: '${{ github.token }}' - run: | - repo=${{ github.repository }} - reviewDecision="$(gh api graphql -F owner=${{ github.repository_owner }} -F name=${repo##*/} -F pr_number=${{ github.event.pull_request.number }} -f query=' - query($name: String!, $owner: String!, $pr_number: Int!) { - repository(owner: $owner, name: $name) { - pullRequest(number: $pr_number) { - reviewDecision - } - } - } - ' --jq '.data.repository.pullRequest.reviewDecision')" - - echo REVIEW_DECISION=$reviewDecision >> $GITHUB_OUTPUT - - # Only run Tool request cleanup when the pull request is approved. - cleanup: - needs: 'review_status' - if: '${{ needs.review_status.outputs.REVIEW_DECISION == ''APPROVED'' }}' - runs-on: 'ubuntu-latest' - permissions: - contents: 'read' - id-token: 'write' - pull-requests: 'write' - name: 'Handle Tool Request Cleanup' - steps: - - name: 'Checkout Triggering Branch' - uses: 'actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab' # ratchet:actions/checkout@v3 - with: - ref: '${{ github.event.pull_request.head.ref }}' - - name: 'Setup Go' - uses: 'actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568' # ratchet:actions/setup-go@v3 - with: - go-version: '${{ inputs.go_version }}' - - name: 'Authenticate to Google Cloud' - uses: 'google-github-actions/auth@35b0e87d162680511bf346c299f71c9c5c379033' # ratchet:google-github-actions/auth@v1 - with: - workload_identity_provider: '${{ inputs.workload_identity_provider }}' - service_account: '${{ inputs.service_account }}' - token_format: 'access_token' - # Install gcloud, `setup-gcloud` automatically picks up authentication from `auth`. - - name: 'Set up Cloud SDK' - uses: 'google-github-actions/setup-gcloud@e30db14379863a8c79331b04a9969f4c1e225e0b' # ratchet:google-github-actions/setup-gcloud@v1 - - name: 'Install AOD CLI' - run: 'go install github.com/abcxyz/access-on-demand/cmd/aod@${{ inputs.aod_cli_version }}' - - name: 'Handle cleanup' - id: 'cleanup_tool' - env: - FILE_PATH: '${{ github.workspace }}/tool.yaml' - run: | - touch ${{ env.TOOL_ERROR_FILENAME }} ${{ env.TOOL_OUT_FILENAME }} - aod tool cleanup -path ${{ env.FILE_PATH }} \ - 2> ${{ env.TOOL_ERROR_FILENAME }} \ - > ${{ env.TOOL_OUT_FILENAME }} - # TODO (#79): Output only executed commands. - - name: 'Tool Request Cleanup Comment' - if: '${{ always() }}' - uses: 'actions/github-script@98814c53be79b1d30f795b907e553d8679345975' # ratchet:actions/github-script@v6 - with: - github-token: '${{ github.token }}' - retries: '3' - script: |+ - var body, req; - const fs = require("fs"); - const outcome = '${{ steps.cleanup_tool.outcome }}'; - switch (outcome) { - case 'success': - req = fs.readFileSync( - `${{ env.TOOL_OUT_FILENAME }}`, - { encoding: "utf8" } - ); - - body = `**\`Access on Demand\`** - 🟩 **\`Tool\`** request succeeded. - -
- Details - Executed "cleanup" commands in the request below, or skipped if "cleanup" commands not found. - - \`\`\` - ${req} - \`\`\` -
`; - break; - case 'failure': - req = fs.readFileSync( - `${{ env.TOOL_OUT_FILENAME }}`, - { encoding: "utf8" } - ); - const error = fs.readFileSync( - `${{ env.TOOL_ERROR_FILENAME }}`, - { encoding: "utf8" } - ); - body = `**\`Access on Demand\`** - 🟥 **\`Tool\`** request failed. - -
- Details - Failed to execute "cleanup" commands in the request below. - - \`\`\` - ${req} - \`\`\` - - Error: - \`\`\` - ${error} - \`\`\` -
`; - break; - // step cancelled/skipped, should not happen if the triggering event is correct. - default: - // Do nothing. - break; - } - - if (typeof body !== "undefined") { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: ${{ github.event.pull_request.number }}, - body: body, - }); - }