Repository: aws-actions/configure-aws-credentials (ts)

Issue:
Invalid tag when assuming role
Hi 👋
I've just been trying to use the new [role-assuming feature](https://github.com/aws-actions/configure-aws-credentials/pull/17) in master, but I've run into an issues with the session tagging.

I'm getting the following error:
```
##[error]1 validation error detected: Value 'dependabot-preview[bot]' at 'tags.5.member.value' failed to satisfy constraint: Member must satisfy regular expression pattern: [\p{L}\p{Z}\p{N}_.:/=+\-@]*
##[error]Node run failed with exit code 1
```

My theory is that this is caused by tagging the session with the `GITHUB_ACTOR` environment variable.
In [this line](https://github.com/aws-actions/configure-aws-credentials/blob/master/index.js#L41), the session is tagged with `GITHUB_ACTOR`.

The actor of the run (and so `GITHUB_ACTOR`) was `dependabot-preview[bot]`.
It seems that `[` and `]` are not valid characters in AWS session tags, and so this is crashing the program.

Seems like the tag must be removed or the variable sanitised.

Pull request description:
fix: sanitize AWS session tags
Closes #18 

Ensure that alls tags applied to the AWS role session are valid.

For `GITHUB_ACTOR`, `[` and `]` must be removed.

For `GITHUB_WORKFLOW` I've inverted the [tag value requirement regex](https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html), `[\p{L}\p{Z}\p{N}_.:/=+\-@]+`, and replaced any characters that don't conform. I've also truncated the value as it has a maximum of 256 chars.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Patch:
diff --git a/README.md b/README.md
index 47e739c..ef6fbfe 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,39 @@ We recommend following [Amazon IAM best practices](https://docs.aws.amazon.com/I
 * [Rotate the credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#rotate-credentials) used in GitHub Actions workflows regularly.
 * [Monitor the activity](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#keep-a-log) of the credentials used in GitHub Actions workflows.
 
+## Assuming a role
+If you would like to use the credentials you provide to this action to assume a role, you can do so by specifying the role ARN in `role-to-assume`.
+The role credentials will then be output instead of the ones you have provided.  
+The default session duration is 6 hours, but if you would like to adjust this you can pass a duration to `role-duration-seconds`.
+
+Example:
+```yaml
+    - name: Configure AWS Credentials
+      uses: aws-actions/configure-aws-credentials@v1
+      with:
+        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+        aws-region: us-east-2
+        role-to-assume: arn:aws:iam::123456789100:role/role-to-assume
+        role-duration-seconds: 1200
+```
+
+### Session tagging
+The session will have the name "GitHubActions" and be tagged with the following tags:  
+(`GITHUB_` environment variable definitions can be [found here](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-environment-variables#default-environment-variables))
+
+| Key | Value|
+| --- | --- |
+| GitHub | "Actions" |
+| Repository | GITHUB_REPOSITORY |
+| Workflow | GITHUB_WORKFLOW |
+| Action | GITHUB_ACTION |
+| Actor | GITHUB_ACTOR |
+| Branch | GITHUB_REF |
+| Commit | GITHUB_SHA |
+
+_Note: all tag values must conform to [the requirements](https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html). Particularly, `GITHUB_WORKFLOW` will be truncated if it's too long. If `GITHUB_ACTOR` or `GITHUB_WORKFLOW` contain invalid charcters, the characters will be replaced with an '*'._
+
 ## License Summary
 
 This code is made available under the MIT license.
diff --git a/buildspec.yml b/buildspec.yml
new file mode 100644
index 0000000..a3654da
--- /dev/null
+++ b/buildspec.yml
@@ -0,0 +1,14 @@
+version: 0.2
+
+phases:
+  install:
+    runtime-versions:
+      nodejs: 10
+  build:
+    commands:
+      # Run unit tests
+      - npm ci
+      - npm run test
+artifacts:
+  files:
+    - '**/*'
diff --git a/buildspec_check_dist.yml b/buildspec_check_dist.yml
new file mode 100644
index 0000000..df9d77d
--- /dev/null
+++ b/buildspec_check_dist.yml
@@ -0,0 +1,18 @@
+version: 0.2
+
+phases:
+  install:
+    runtime-versions:
+      nodejs: 10
+  build:
+    commands:
+      # Validate that the dist/ folder has the latest code packaged.
+      # If there are unpackaged changes, this build will fail.
+      # The GitHub Actions 'package' workflow will eventually run
+      # and will sync the dist folder, which will re-trigger this build.
+      - npm ci
+      - npm run package
+      - git diff --exit-code dist/index.js
+artifacts:
+  files:
+    - '**/*'
diff --git a/buildspec_integ.yml b/buildspec_integ.yml
new file mode 100644
index 0000000..236052e
--- /dev/null
+++ b/buildspec_integ.yml
@@ -0,0 +1,24 @@
+version: 0.2
+
+env:
+  variables:
+    GITHUB_HUB_CLI_VERSION: 2.13.0
+
+phases:
+  install:
+    runtime-versions:
+      nodejs: 10
+  build:
+    commands:
+      # Install hub CLI
+      - wget https://github.com/github/hub/releases/download/v$GITHUB_HUB_CLI_VERSION/hub-linux-amd64-$GITHUB_HUB_CLI_VERSION.tgz
+      - tar -xzvf hub-linux-amd64-$GITHUB_HUB_CLI_VERSION.tgz && mv hub-linux-amd64-$GITHUB_HUB_CLI_VERSION/bin/hub /usr/local/bin
+
+      # Configure git client
+      - git config user.name "CodeBuild Integration Test"
+      - git config user.email $COMMIT_EMAIL_ADDRESS
+      - git config credential.helper "store --file=.git/credentials"
+      - echo "https://${GITHUB_TOKEN}:@github.com" > .git/credentials
+
+      # Run integration tests
+      - ./run_integ_test.sh
diff --git a/index.js b/index.js
index 744f8ea..b6afab0 100644
--- a/index.js
+++ b/index.js
@@ -7,6 +7,8 @@ const util = require('util');
 // That seems like a reasonable default to use if no role duration is defined.
 const MAX_ACTION_RUNTIME = 6 * 3600;
 const USER_AGENT = 'configure-aws-credentials-for-github-actions';
+const MAX_TAG_VALUE_LENGTH = 256;
+const SANITIZATION_CHARACTER = '*'
 
 async function assumeRole(params) {
   // Assume a role to get short-lived credentials using longer-lived credentials.
@@ -36,9 +38,9 @@ async function assumeRole(params) {
     Tags: [
       {Key: 'GitHub', Value: 'Actions'},
       {Key: 'Repository', Value: GITHUB_REPOSITORY},
-      {Key: 'Workflow', Value: GITHUB_WORKFLOW},
+      {Key: 'Workflow', Value: sanitizeGithubWorkflowName(GITHUB_WORKFLOW)},
       {Key: 'Action', Value: GITHUB_ACTION},
-      {Key: 'Actor', Value: GITHUB_ACTOR},
+      {Key: 'Actor', Value: sanitizeGithubActor(GITHUB_ACTOR)},
       {Key: 'Branch', Value: GITHUB_REF},
       {Key: 'Commit', Value: GITHUB_SHA},
     ]
@@ -53,6 +55,21 @@ async function assumeRole(params) {
   });
 }
 
+function sanitizeGithubActor(actor) {
+  // In some circumstances the actor may contain square brackets. For example, if they're a bot ('[bot]')
+  // Square brackets are not allowed in AWS session tags
+  return actor.replace(/\[|\]/g, SANITIZATION_CHARACTER)
+}
+
+function sanitizeGithubWorkflowName(name) {
+  // Workflow names can be almost any valid UTF-8 string, but tags are more restrictive.
+  // This replaces anything not conforming to the tag restrictions by inverting the regular expression.
+  // See the AWS documentation for constraint specifics https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html.
+  const nameWithoutSpecialCharacters = name.replace(/[^\p{L}\p{Z}\p{N}_.:/=+-@]/gu, SANITIZATION_CHARACTER);
+  const nameTruncated = nameWithoutSpecialCharacters.slice(0, MAX_TAG_VALUE_LENGTH)
+  return nameTruncated
+}
+
 function exportCredentials(params){
   // Configure the AWS CLI and AWS SDKs using environment variables and set them as secrets.
   // Setting the credentials as secrets masks them in Github Actions logs


Test patch:
diff --git a/index.test.js b/index.test.js
index 9db2f8d..9b8617e 100644
--- a/index.test.js
+++ b/index.test.js
@@ -19,10 +19,11 @@ const ENVIRONMENT_VARIABLE_OVERRIDES = {
     GITHUB_REPOSITORY: 'MY-REPOSITORY-NAME',
     GITHUB_WORKFLOW: 'MY-WORKFLOW-ID',
     GITHUB_ACTION: 'MY-ACTION-NAME',
-    GITHUB_ACTOR: 'MY-USERNAME',
+    GITHUB_ACTOR: 'MY-USERNAME[bot]',
     GITHUB_REF: 'MY-BRANCH',
     GITHUB_SHA: 'MY-COMMIT-ID',
 };
+const GITHUB_ACTOR_SANITIZED = 'MY-USERNAME*bot*'
 
 function mockGetInput(requestResponse) {
     return function (name, options) { // eslint-disable-line no-unused-vars
@@ -208,7 +209,7 @@ describe('Configure AWS Credentials', () => {
                 {Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY},
                 {Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW},
                 {Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION},
-                {Key: 'Actor', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTOR},
+                {Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED},
                 {Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF},
                 {Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA},
             ]
@@ -230,7 +231,33 @@ describe('Configure AWS Credentials', () => {
                 {Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY},
                 {Key: 'Workflow', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_WORKFLOW},
                 {Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION},
-                {Key: 'Actor', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTOR},
+                {Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED},
+                {Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF},
+                {Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA},
+            ]
+        })
+    });
+
+    test('workflow name sanitized in role assumption tags', async () => {
+        core.getInput = jest
+            .fn()
+            .mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS));
+        
+        process.env = {...process.env, GITHUB_WORKFLOW: 'Workflow!"#$%&\'()*+, -./:;<=>?@[]^_`{|}~🙂💥🍌1yFvMOeD3ZHYsHrGjCceOboMYzBPo0CRNFdcsVRG6UgR3A912a8KfcBtEVvkAS7kRBq80umGff8mux5IN1y55HQWPNBNyaruuVr4islFXte4FDQZexGJRUSMyHQpxJ8OmZnET84oDmbvmIjgxI6IBrdihX9PHMapT4gQvRYnLqNiKb18rEMWDNoZRy51UPX5sWK2GKPipgKSO9kqLckZai9D2AN2RlWCxtMqChNtxuxjqeqhoQZo0oaq39sjcRZgAAAAAAA'};
+
+        const sanitizedWorkflowName = 'Workflow**********+, -./:;<=>?@***_********1yFvMOeD3ZHYsHrGjCceOboMYzBPo0CRNFdcsVRG6UgR3A912a8KfcBtEVvkAS7kRBq80umGff8mux5IN1y55HQWPNBNyaruuVr4islFXte4FDQZexGJRUSMyHQpxJ8OmZnET84oDmbvmIjgxI6IBrdihX9PHMapT4gQvRYnLqNiKb18rEMWDNoZRy51UPX5sWK2GKPipgKSO9kqLckZa'
+
+        await run();
+        expect(mockStsAssumeRole).toHaveBeenCalledWith({
+            RoleArn: ROLE_NAME,
+            RoleSessionName: 'GitHubActions',
+            DurationSeconds: 6 * 3600,
+            Tags: [
+                {Key: 'GitHub', Value: 'Actions'},
+                {Key: 'Repository', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REPOSITORY},
+                {Key: 'Workflow', Value: sanitizedWorkflowName},
+                {Key: 'Action', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_ACTION},
+                {Key: 'Actor', Value: GITHUB_ACTOR_SANITIZED},
                 {Key: 'Branch', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_REF},
                 {Key: 'Commit', Value: ENVIRONMENT_VARIABLE_OVERRIDES.GITHUB_SHA},
             ]
diff --git a/run_integ_test.sh b/run_integ_test.sh
new file mode 100755
index 0000000..37bdaa7
--- /dev/null
+++ b/run_integ_test.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+set -ex
+
+# Retrieve the action commit ID to test
+ACTION_GIT_COMMIT_ID=`git rev-parse HEAD`
+
+# Update the integ test action workflow file with the latest action commit ID
+git checkout integ-tests
+sed -i 's|aws-actions/configure-aws-credentials@v1|aws-actions/configure-aws-credentials@$ACTION_GIT_COMMIT_ID|g' test-workflow.yml
+cp test-workflow.yml .github/workflows
+git add .github/workflows
+git commit -m "Test commit $ACTION_GIT_COMMIT_ID"
+
+# Trigger the action workflow
+git push origin integ-tests
+
+# Validate that the action workflow succeeds
+# Exit codes: success = 0; failure = 1; pending = 2; no status = 3
+while hub ci-status; [ $? -ge 2 ] do
+  echo "waiting for test workflow to complete..."
+  sleep 5
+done
+
+hub ci-status