지난번에 게시한 AWS Signature v4를 이용한 S3 객체 다운로드/업로드의 ShellScript도 함께 작성하게 되었다.

(AWS Signature 소개 및 PowerShell 버전 참조)

ShellScript 버전은 PowerShell보다 참조할 자료가 많아 AWS4-HMAC-SHA256 알고리즘 구현이 쉬웠고, 필요했던 기능인 s3에 대해서만 객체 다운로드/업로드 기능을 구현했다.

#!bin/bash

## Set my credential ##
ACCESS_KEY=###############
SECRET_KEY=#########################
REGION=ap-northeast-2

urlEncode() {
  LINE="$1"
  LENGTH="${#LINE}"
  I=0
  while [ $I -lt $LENGTH ]
    do
    C="${LINE:I:1}"
  case $C in
    [a-zA-Z0-9.~_-]) printf "$C" ;;
    *) printf '%%%02X' "'$C" ;;
  esac
    let I=I+1
  done
}

getHexaDecimalString() {
  read LINE
  LENGTH="${#LINE}"
  I=0
  while [ $I -lt $LENGTH ]
    do
    C="${LINE:I:1}"
   printf '%2x' "'$C"
   let I=I+1
  done
}

getSignatureKey() {
  SECRET_KEY=$1
  DATESTAMP=$2
  REGIONNAME=$3
  SERVICENAME=$4
  STRING_TO_SIGN=$5

  HEX_KEY=$(echo -n "AWS4${SECRET_KEY}" | getHexaDecimalString)
  HEX_KEY=$(echo -n "${DATESTAMP}" | openssl dgst -sha256 -mac HMAC -macopt hexkey:${HEX_KEY})
  HEX_KEY=$(echo -n "${REGIONNAME}" | openssl dgst -sha256 -mac HMAC -macopt hexkey:${HEX_KEY#* })
  HEX_KEY=$(echo -n "${SERVICENAME}" | openssl dgst -sha256 -mac HMAC -macopt hexkey:${HEX_KEY#* })
  SIGNING_KEY=$(echo -n "aws4_request" | openssl dgst -sha256 -mac HMAC -macopt hexkey:${HEX_KEY#* })

  SIGNATURE=$(echo -en "${STRING_TO_SIGN}" | openssl dgst -binary -hex -sha256 -mac HMAC -macopt hexkey:${SIGNING_KEY#* })
  echo "${SIGNATURE#* }"
}

getHexaHash() {
  PAYLOAD="$@"
  HASH=$(echo -n "${PAYLOAD}" | openssl dgst -sha256)
  echo  "${HASH#* }"
}

### Main ###

if [ -z $REGION ] || [ -z $ACCESS_KEY ] || [ -z $SECRET_KEY ]
then
  echo "Please set ACCESS_KEY, SECRET_KEY and REGION variables"
exit 1
fi

AK="$ACCESS_KEY"
SK="$SECRET_KEY"
REGION="$REGION"

[ $# -ne 8 ] && exit 2

while getopts ":m:b:k:e:" OPT; do
  case $OPT in
        m)
         HTTPMETHOD=$OPTARG # GET or PUT
         ;;
        b)
         BUCKET=$OPTARG
         ;;
        k)
         S3KEY=$OPTARG
         ;;
        e)
         EXPIRES=$OPTARG
         ;;
       *)
   echo "Invalid option: -$OPTARG" >&2
   exit 3
   ;;
   esac
done

SERVICENAME="s3"
HOST="${BUCKET}.${SERVICENAME}.${REGION}.amazonaws.com"
ENDPOINT="https://${BUCKET}.${SERVICENAME}.${REGION}.amazonaws.com"

# step 1. Create a Canonical request
AMZ_DATE=$(date -u +%Y%m%dT%H%M%SZ)
DATESTAMP=$(date -u +%Y%m%d)
AMZ_EXPIRES=$((${EXPIRES}*60))   # min

CANONICAL_URI="/${S3KEY}"

CANONICAL_HEADERS="host:${HOST}\n"
SIGNED_HEADERS="host"
PAYLOAD_HASH="UNSIGNED-PAYLOAD"

ALGORITHM="AWS4-HMAC-SHA256"
CREDENTIAL_SCOPE="${DATESTAMP}/${REGION}/${SERVICENAME}/aws4_request"

CANONICAL_QUERYSTRING="X-Amz-Algorithm=${ALGORITHM}"
CANONICAL_QUERYSTRING="${CANONICAL_QUERYSTRING}&X-Amz-Credential=$(urlEncode "${AK}/${CREDENTIAL_SCOPE}")"
CANONICAL_QUERYSTRING="${CANONICAL_QUERYSTRING}&X-Amz-Date=${AMZ_DATE}"
CANONICAL_QUERYSTRING="${CANONICAL_QUERYSTRING}&X-Amz-Expires=${AMZ_EXPIRES}"
CANONICAL_QUERYSTRING="${CANONICAL_QUERYSTRING}&X-Amz-SignedHeaders=${SIGNED_HEADERS}"
CANONICAL_REQUEST="${HTTPMETHOD}\n${CANONICAL_URI}\n${CANONICAL_QUERYSTRING}\n${CANONICAL_HEADERS}\n${SIGNED_HEADERS}\n${PAYLOAD_HASH}"

# step 2. String To Sign
STRING_TO_SIGN="${ALGORITHM}\n${AMZ_DATE}\n${CREDENTIAL_SCOPE}\n$(getHexaHash "$(echo -e "${CANONICAL_REQUEST}")")"

# step 3. Signature
SIGNATURE="$(getSignatureKey $SK $DATESTAMP $REGION $SERVICENAME $STRING_TO_SIGN)"

# step 4.  Create a request URL
CANONICAL_QUERYSTRING="${CANONICAL_QUERYSTRING}&X-Amz-Signature=${SIGNATURE}"

REQUEST_URL=${ENDPOINT}/${S3KEY}?${CANONICAL_QUERYSTRING}

CLI="curl $(if [[ "$HTTPMETHOD" == "GET" ]]; then echo "-o"; else echo "-T"; fi) \"${S3KEY##*/}\" \"$REQUEST_URL\""
echo "$CLI"

사용방법은 ACCESS_KEY, SECRET_KEY, REGION 값을 자신의 설정에 알맞게 입력하고 쉘 스크립트 실행 시 argument 값을 각각 지정하면 해당 파라미터에 대해 curl + Request URL을 출력한다.

실행 시 입력해야 하는 argument 목록은 다음과 같다.

Argument Value
m (method) GET 또는 PUT (다운로드/업로드)
b (bucket) Bucket Name
k (key) Key Name
e (expires) 120 (분)

(ex. bash s3web.sh  -m GET -b s3-mybucket -k mydir/myobj -e 120)

PowerShell 버전과는 달리 실행 시 인자 값을 입력받도록 했는데, 필요에 따라 입력받는 부분을 고정 값으로 변경하여 사용하면 된다.

만약 Request URL 실행문을 출력하는 대신 스크립트 자체에서 실행되도록 하고 싶다면 마지막 줄에 eval "${CLI}" 를 추가하도록 한다.