Skip to content

Instantly share code, notes, and snippets.

@StevenACoffman
Created September 27, 2018 21:48
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save StevenACoffman/f0c084b428977430d2baacd0263c3563 to your computer and use it in GitHub Desktop.
Save StevenACoffman/f0c084b428977430d2baacd0263c3563 to your computer and use it in GitHub Desktop.
Rotate Github Personal Access Keys
#!/bin/bash
TOKEN_UUID=$(uuidgen)
if [ -z ${GITHUB_USERID+x} ]; then
echo "GITHUB_USERID is unset, exitting";
exit 1
else
echo "GITHUB_USERID is set to '$GITHUB_USERID'"
fi
if [ -z ${GITHUB_AUTH_TOKEN_ID+x} ]; then
echo "GITHUB_AUTH_TOKEN_ID is unset, so cannot proceed.";
else
echo "GITHUB_AUTH_TOKEN_ID is already set, so rotating personal access token"
GITHUB_URL="https://api.github.com/authorizations"
# store the whole response with the status at the and
HTTP_RESPONSE=$(curl --silent --write-out "HTTPSTATUS:%{http_code}" \
--user "${GITHUB_USERID}" \
--data '{"scopes":["repo"],"note":"Rotate-Creds-Demo'"${TOKEN_UUID}"'"}' \
$GITHUB_URL)
# extract the body
HTTP_BODY=$(echo "$HTTP_RESPONSE" | sed -e 's/HTTPSTATUS\:.*//g')
# extract the status
HTTP_STATUS=$(echo "$HTTP_RESPONSE" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
# print the body
echo "$HTTP_BODY"
# example using the status
if [ $HTTP_STATUS -eq 401 ]; then
echo "Recieved 401, so trying again with Multifactor Auth OTP"
echo -n "Enter Multi-Factor Auth code for $GITHUB_USERID: "
read -s GITHUB_OTP
echo ""
HTTP_RESPONSE=$(curl --silent --write-out "HTTPSTATUS:%{http_code}" \
-H "X-GitHub-OTP: ${GITHUB_OTP}" \
--user "${GITHUB_USERID}" \
--data '{"scopes":["repo"],"note":"Rotate-Creds-Demo'"${TOKEN_UUID}"'"}' \
$GITHUB_URL)
# extract the body
HTTP_BODY=$(echo "$HTTP_RESPONSE" | sed -e 's/HTTPSTATUS\:.*//g')
# extract the status
HTTP_STATUS=$(echo "$HTTP_RESPONSE" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
fi
if [ ! $HTTP_STATUS -eq 201 ]; then
echo "Error [HTTP status: $HTTP_STATUS]"
echo "RESPONSE:"
echo "$HTTP_BODY"
exit 1
fi
#Note: Do not overwrite existing variables yet
GITHUB_NEW_AUTH_TOKEN=$(echo "$HTTP_BODY" | jq -r .token)
GITHUB_NEW_AUTH_TOKEN_ID=$(echo "$HTTP_BODY" | jq -r .id)
GITHUB_NEW_AUTH_TOKEN_NOTE=$(echo "$HTTP_BODY" | jq -r .note)
echo "RESPONSE:"
echo "$HTTP_BODY"
echo 'export GITHUB_AUTH_TOKEN="'"$GITHUB_NEW_AUTH_TOKEN"'"' > secret_token.sh
echo 'export GITHUB_AUTH_TOKEN_ID="'"$GITHUB_NEW_AUTH_TOKEN_ID"'"' >> secret_token.sh
echo 'export GITHUB_AUTH_TOKEN_NOTE="'"$GITHUB_NEW_AUTH_TOKEN_NOTE"'"' >> secret_token.sh
chmod u+x secret_token.sh
echo "Wrote new GITHUB_AUTH_TOKEN to secret_token.sh"
HTTP_RESPONSE=$(curl --silent --write-out "HTTPSTATUS:%{http_code}" \
--user "${GITHUB_USERID}" \
-X DELETE "${GITHUB_URL}/${GITHUB_AUTH_TOKEN_ID}")
# extract the body
HTTP_BODY=$(echo "$HTTP_RESPONSE" | sed -e 's/HTTPSTATUS\:.*//g')
# extract the status
HTTP_STATUS=$(echo "$HTTP_RESPONSE" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
# example using the status
if [ $HTTP_STATUS -eq 401 ]; then
echo "Recieved 401, so trying again with Multifactor Auth OTP"
echo -n "Enter Multi-Factor Auth code for $GITHUB_USERID: "
read -s GITHUB_OTP
echo ""
HTTP_RESPONSE=$(curl --silent --write-out "HTTPSTATUS:%{http_code}" \
-H "X-GitHub-OTP: ${GITHUB_OTP}" \
--user "${GITHUB_USERID}" \
-X DELETE "${GITHUB_URL}/${GITHUB_AUTH_TOKEN_ID}")
# extract the body
HTTP_BODY=$(echo "$HTTP_RESPONSE" | sed -e 's/HTTPSTATUS\:.*//g')
# extract the status
HTTP_STATUS=$(echo "$HTTP_RESPONSE" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
fi
if [ ! $HTTP_STATUS -eq 204 ]; then
echo "Error [HTTP status: $HTTP_STATUS]"
echo "RESPONSE:"
echo "$HTTP_BODY"
exit 1
fi
echo "Successfully deleted old github personal access tokens"
git ignore secret_token.sh
echo "Git ignored secret_token.sh"
fi
@StevenACoffman
Copy link
Author

Left it ugly but working for now. Just a proof of concept. I'll redo it in golang as an aws lambda using parameter store when we do it for real. I've got neat tricks for the MFA bits.

@karlw00t
Copy link

karlw00t commented Jun 7, 2019

@StevenACoffman
Copy link
Author

@karlw00t I've used both secrets manager and parameter store, and I've seen that code. My use case was handling the tricky interaction with the github api, which that code doesn't address AFAICT.

@reveller
Copy link

reveller commented Aug 3, 2020

@StevenACoffman Were you ever able to solve this? I am in a very similar situation where I need to be able to use a service account to generate GitHub access tokens for rotation. I am planning to use AWS Secrets Manager and a rotation Lambda to perform the rotation. Having trouble with the GitHub side of things.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment