diff --git a/.github/workflows/build-private-ca-on-push.yaml b/.github/workflows/build-private-ca-on-push.yaml new file mode 100644 index 0000000..0004263 --- /dev/null +++ b/.github/workflows/build-private-ca-on-push.yaml @@ -0,0 +1,26 @@ +name: Build Private CA on Push + +on: + push: + paths: + - private-ca/client/Docker/** + - .github/workflows/build-private-ca-on-push.yml + +jobs: + + docker_publish: + runs-on: "ubuntu-22.04" + steps: + - uses: actions/checkout@v3 + - name: Build Docker Image + run: | + cd private-ca/client/Docker/ + [[ "$GITHUB_REF_NAME" == "main" ]] && export TAG="latest" || TAG="$GITHUB_REF_NAME" + docker build . --tag ghcr.io/getfundwave/network-utils/private-ca:$TAG + - name: Publish docker image + env: + TOKEN: ${{secrets.GITHUB_TOKEN}} + run: | + echo $TOKEN | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin + [[ "$GITHUB_REF_NAME" == "main" ]] && export TAG="latest" || TAG="$GITHUB_REF_NAME" + docker push ghcr.io/getfundwave/network-utils/private-ca:$TAG diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0979a6c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +**/node_modules/** diff --git a/.talismanrc b/.talismanrc index 89a826c..30c03e0 100644 --- a/.talismanrc +++ b/.talismanrc @@ -2,4 +2,4 @@ threshold: medium fileignoreconfig: - filename: aws-credentials-utils/store-credentials.sh checksum: 784aec6e80314be796af73887ba630fbaf0e116cc4d11bd5cba787a20f9c4bbb -version: "" \ No newline at end of file +version: "" diff --git a/private-ca/README.md b/private-ca/README.md new file mode 100644 index 0000000..0f85d5a --- /dev/null +++ b/private-ca/README.md @@ -0,0 +1,43 @@ +# Private Certificate Authority (CA) for SSH and SSL Certificates + +This project provides a private Certificate Authority (CA) implementation for generating both SSH and SSL certificates. It allows you to issue certificates for SSH hosts and users, as well as client SSL certificates for secure communication. + +## Installation + + +Deploy the resources by running: + + ```bash + ./deploy-server-on-lambda.sh + ``` + +This creates the following resources on AWS: +- Secret to store the keys for signing certificates +- A role for the lambda function +- A policy to be attached to the role giving read access to created secret +- An openSSH layer to facilitate SSH operations +- The lambda function to act as a privateCA + +Note: Once the lambda is deployed you will need to manually add an environment variable called `AWS_SCRTS_REGION` to store the region in which AWS secrets for privateCA reside. + +## Usage + +Certificates can be generated by running: + + ```bash + cd client/Docker + ./generate-certificate-curl.sh + ``` + +You can pass the following params to modify the payload: + +- `CA_ACTION` + - generateHostSSHCert + - generateClientSSHCert + - generateClientX509Cert +- `CA_LAMBDA_URL`: The URL of the AWS Lambda function hosting the Private CA. +- `USER_SSH_DIR`: The path to the directory where the user's SSH keys will be stored. Defaults to "/home/$USER/.ssh". +- `SYSTEM_SSH_DIR`: The path to the system's SSH directory. Defaults to "/etc/ssh". +- `SYSTEM_SSL_DIR`: The path to the system's SSL directory. Defaults to "/etc/ssl". +- `AWS_STS_REGION`: The AWS region for the STS (Security Token Service). Defaults to "ap-south-1". +- `AWS_PROFILE`: The AWS profile for running aws commands. Defaults to "default" \ No newline at end of file diff --git a/private-ca/client/Docker/Dockerfile b/private-ca/client/Docker/Dockerfile new file mode 100644 index 0000000..f2bb956 --- /dev/null +++ b/private-ca/client/Docker/Dockerfile @@ -0,0 +1,15 @@ +FROM alpine:latest + +RUN apk add bash curl unzip groff jq python3 openssh openssl +RUN python3 -m ensurepip +RUN pip3 install boto3 + +WORKDIR /app +COPY generate-certificate-curl.sh /app +COPY run.sh /app +COPY aws-auth-header.py /app + +RUN chmod +x generate-certificate-curl.sh +RUN chmod +x run.sh +ENV CURL_CA_BUNDLE="/etc/pki/tls/certs/ca-bundle.crt" +ENTRYPOINT ["/app/run.sh" ] diff --git a/private-ca/client/Docker/README.md b/private-ca/client/Docker/README.md new file mode 100644 index 0000000..cdc5d1f --- /dev/null +++ b/private-ca/client/Docker/README.md @@ -0,0 +1,63 @@ +# Certificate Generator Docker Container + +This Docker container runs a script that generates SSH and X.509 certificates using a Private Certificate Authority (CA) hosted on AWS Lambda. The script provides options to generate SSH certificates for hosts and clients, as well as X.509 certificates for clients. + +## Prerequisites + +- Docker: Install Docker on your system. Refer to the [Docker documentation](https://docs.docker.com/get-docker/) for installation instructions. + +## Usage + +1. Clone this repository or download the Dockerfile and the `generate-certificate.sh` script. + +2. Build the Docker image using the following command: + + ```shell + docker build -t generate-certificate . + ``` + +3. Run the Docker container with the desired parameters. The container requires specific environment variables to be set: + + - `CA_ACTION`: Specify the action to perform. Possible values are: + - `generateHostSSHCert`: Generates an SSH certificate for the host. + - `generateClientSSHCert`: Generates an SSH certificate for a client. + - `generateClientX509Cert`: Generates an X.509 certificate for a client. + + - `CA_LAMBDA_URL`: The URL of the AWS Lambda function hosting the Private CA. + + Optional environment variables: + - `USER_SSH_DIR`: The path to the directory where the user's SSH keys will be stored. Defaults to "/home/$USER/.ssh". + - `SYSTEM_SSH_DIR`: The path to the system's SSH directory. Defaults to "/etc/ssh". + - `SYSTEM_SSL_DIR`: The path to the system's SSL directory. Defaults to "/etc/ssl". + - `AWS_STS_REGION`: The AWS region for the STS (Security Token Service). Defaults to "ap-south-1". + - `AWS_PROFILE`: The AWS profile for running aws commands. Defaults to "default" + + ```shell + docker run -it --rm \ + -v /path/to/ssh/directory:/home/$USER/.ssh \ + -v /path/to/system/ssh/directory:/etc/ssh \ + -v /path/to/system/ssl/directory:/etc/ssl \ + -e CA_ACTION= \ + -e CA_LAMBDA_URL= \ + -e USER_SSH_DIR= \ + -e SYSTEM_SSH_DIR= \ + -e SYSTEM_SSL_DIR= \ + -e AWS_STS_REGION= \ + -e AWS_PROFILE= \ + generate-certificate + ``` + +4. The script will generate the necessary certificates based on the provided action and store them in the specified directories. + +## Script Explanation + +The `generate-certificate.sh` script performs the following tasks: + +- Parses command-line arguments and checks for the specified CA action. +- Checks if the required SSH or X.509 certificate already exists and is valid. If not, generates new certificates. +- Retrieves temporary AWS credentials using STS (Security Token Service). +- Generates authentication headers required for making authenticated AWS API requests. +- Invokes the AWS Lambda function to generate the certificate based on the specified action. +- Stores the generated certificate in the appropriate directory. + +Make sure to adjust the script parameters and environment variables according to your specific requirements. diff --git a/private-ca/client/Docker/aws-auth-header.py b/private-ca/client/Docker/aws-auth-header.py new file mode 100644 index 0000000..446aca7 --- /dev/null +++ b/private-ca/client/Docker/aws-auth-header.py @@ -0,0 +1,30 @@ +import sys +from datetime import datetime +import boto3 +from botocore.auth import SigV4Auth +from botocore.awsrequest import AWSRequest +from botocore.credentials import Credentials + +if __name__ == "__main__": + access_key_id = sys.argv[1] + secret_access_key = sys.argv[2] + session_token = sys.argv[3] + aws_region = sys.argv[4] + + sts_host = "sts." + aws_region + ".amazonaws.com" + request_parameters = 'Action=GetCallerIdentity&Version=2011-06-15' + request_headers = { + 'Host': sts_host, + 'X-Amz-Date': datetime.now().strftime('%Y%m%dT%H%M%SZ'), + 'Aud': 'FundwaveCA' + } + request = AWSRequest(method="POST", url="/", data=request_parameters, headers=request_headers) + boto_creds = Credentials(access_key_id, secret_access_key,token=session_token) + auth = SigV4Auth(boto_creds, "sts", aws_region) + auth.add_auth(request) + + authorization = request.headers["Authorization"] + date = request.headers["X-Amz-Date"] + + response = f'{{"Authorization": "{authorization}", "Date": "{date}"}}' + print(response) \ No newline at end of file diff --git a/private-ca/client/Docker/generate-certificate-curl.sh b/private-ca/client/Docker/generate-certificate-curl.sh new file mode 100755 index 0000000..c4ddf62 --- /dev/null +++ b/private-ca/client/Docker/generate-certificate-curl.sh @@ -0,0 +1,148 @@ +#!/bin/bash + +CA_ACTION=${1:-$CA_ACTION} +CA_LAMBDA_URL=${2:-$CA_LAMBDA_URL} +USER_SSH_DIR=${3:-"/home/$USER/.ssh"} +SYSTEM_SSH_DIR=${4:-"/etc/ssh"} +SYSTEM_SSL_DIR=${5:-"/etc/ssl"} +AWS_STS_REGION=${6:-"ap-southeast-1"} +AWS_PROFILE=${7:-"default"} + +PYTHON_EXEC=$(which python || which python3) + +# Check for options +while getopts ":h" option; do + case $option in + h) + echo "Usage: ./generate-certificate.sh [ACTION] [PUBLIC KEY FILE] [LAMBDA URL]" + echo "Possible actions:" + echo " generateHostSSHCert: Generates SSH Certificate for Host" + echo " generateClientSSHCert: Generates SSH Certificate for Client" + echo " generateClientX509Cert: Generates X.509 Certificate for Client" + exit;; + *) + echo "Error: Invalid option" + exit;; + esac +done + +# Check for CA Action +if [[ $CA_ACTION = "generateClientSSHCert" ]]; then + if test -f ${USER_SSH_DIR}/id_rsa-cert.pub; then + # Client SSH Certificate already exists + current_timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S") + certificate_expiration_timestamp=$(ssh-keygen -Lf ${USER_SSH_DIR}/id_rsa-cert.pub | awk '/Valid:/{print $NF}') + + if [[ $certificate_expiration_timestamp > $current_timestamp ]]; then + # Certificate is valid + echo "A valid certificate was found at ${USER_SSH_DIR}/id_rsa-cert.pub." + echo "Aborting..." + exit; + else + # Certificate expired + rm ${USER_SSH_DIR}/id_rsa-cert.pub + fi + fi + test -f ${USER_SSH_DIR}/id_rsa.pub || ssh-keygen -t rsa -b 4096 -f ${USER_SSH_DIR}/id_rsa -C host_ca -N "" + CERT_PUBKEY=$(cat ${USER_SSH_DIR}/id_rsa.pub | base64 | tr -d \\n) + +elif [[ $CA_ACTION = "generateHostSSHCert" ]]; then + if test -f ${SYSTEM_SSH_DIR}/ssh_host_rsa_key-cert.pub; then + # Host SSH Certificate already exists + current_timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S") + certificate_expiration_timestamp=$(ssh-keygen -Lf ${SYSTEM_SSH_DIR}/ssh_host_rsa_key-cert.pub | awk '/Valid:/{print $NF}') + + if [[ $certificate_expiration_timestamp > $current_timestamp ]]; then + # Certificate is valid + echo "A valid certificate was found at ${SYSTEM_SSH_DIR}/ssh_host_rsa_key-cert.pub." + echo "Aborting..." + exit; + else + # Certificate expired + rm ${SYSTEM_SSH_DIR}/ssh_host_rsa_key-cert.pub + fi + fi + test -f ${SYSTEM_SSH_DIR}/ssh_host_rsa_key.pub || ssh-keygen -t rsa -b 4096 -f ${SYSTEM_SSH_DIR}/ssh_host_rsa_key -C host_ca -N "" + CERT_PUBKEY=$(cat ${SYSTEM_SSH_DIR}/ssh_host_rsa_key.pub | base64 | tr -d \\n) + +elif [[ $CA_ACTION = "generateClientX509Cert" ]]; then + test -d ${SYSTEM_SSL_DIR}/privateCA || mkdir -p ${SYSTEM_SSL_DIR}/privateCA + if test -f ${SYSTEM_SSL_DIR}/privateCA/public.crt; then + # X.509 Certificate already exists + + if ( openssl x509 -checkend 300 -noout -in ${SYSTEM_SSL_DIR}/privateCA/public.crt ); then + # Certificate is valid for atleast 300 seconds (5 minutes) + echo "A valid certificate was found at ${SYSTEM_SSL_DIR}/privateCA/public.crt." + echo "Aborting..." + exit; + else + # Certificate expired or about to expire + rm ${SYSTEM_SSL_DIR}/privateCA/public.crt + fi + fi + if ! test -f ${SYSTEM_SSL_DIR}/privateCA/public.pem; then + openssl genrsa -out ${SYSTEM_SSL_DIR}/privateCA/key.pem 2048 + openssl rsa -in ${SYSTEM_SSL_DIR}/privateCA/key.pem -outform PEM -pubout -out ${SYSTEM_SSL_DIR}/privateCA/public.pem + fi + CERT_PUBKEY=$(cat ${SYSTEM_SSL_DIR}/privateCA/public.pem | base64 | tr -d \\n) +else + echo "Invalid Action" + echo "Possible actions include:" + echo " generateHostSSHCert: Generates SSH Certificate for Host" + echo " generateClientSSHCert: Generates SSH Certificate for Client" + echo " generateClientX509Cert: Generates X.509 Certificate for Client" + exit; +fi + +# Temporary Credentials +INSTANCE_ROLE_NAME=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/) +TEMP_CREDS=$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/$INSTANCE_ROLE_NAME) + +ACCESS_KEY_ID=$(echo $TEMP_CREDS | jq -r ".AccessKeyId") +SECRET_ACCESS_KEY=$(echo $TEMP_CREDS | jq -r ".SecretAccessKey") +SESSION_TOKEN=$(echo $TEMP_CREDS | jq -r ".Token") + +# Auth Headers +output=$($PYTHON_EXEC aws-auth-header.py $ACCESS_KEY_ID $SECRET_ACCESS_KEY $SESSION_TOKEN $AWS_STS_REGION) +auth_header=$(echo $output | jq -r ".Authorization") +date=$(echo $output | jq -r ".Date") + +EVENT_JSON=$(echo "{\"auth\":{\"amzDate\":\"${date}\",\"authorizationHeader\":\"${auth_header}\",\"sessionToken\":\"${SESSION_TOKEN}\"},\"certPubkey\":\"${CERT_PUBKEY}\",\"action\":\"${CA_ACTION}\",\"awsSTSRegion\":\"${AWS_STS_REGION}\"}") + +if [[ $CA_ACTION = "generateClientSSHCert" ]]; then + LAMBDA_RESPONSE=$(curl "${CA_LAMBDA_URL}" -H 'content-type: application/json' -d "$EVENT_JSON") + ENCODED_CERTIFICATE=$(echo $LAMBDA_RESPONSE | jq -r ".certificate") + CERTIFICATE=$(echo $ENCODED_CERTIFICATE | base64 -d) + HOST_CA_PUBKEY=$(echo $LAMBDA_RESPONSE | jq -r ".\"host_ca.pub\"" | base64 -d) + + echo $CERTIFICATE > ${USER_SSH_DIR}/id_rsa-cert.pub + echo "Certificate written to ${USER_SSH_DIR}/id_rsa-cert.pub" + + if [[ $(grep -q "@cert-authority" "${USER_SSH_DIR}/known_hosts"; echo $?) -ne 0 ]]; then + echo "@cert-authority * ${HOST_CA_PUBKEY}" >> ${USER_SSH_DIR}/known_hosts + fi + +elif [[ $CA_ACTION = "generateHostSSHCert" ]]; then + LAMBDA_RESPONSE=$(curl "${CA_LAMBDA_URL}" -H 'content-type: application/json' -d "$EVENT_JSON") + ENCODED_CERTIFICATE=$(echo $LAMBDA_RESPONSE | jq -r ".certificate") + CERTIFICATE=$(echo $ENCODED_CERTIFICATE | base64 -d) + USER_CA_PUBKEY=$(echo $LAMBDA_RESPONSE | jq -r ".\"user_ca.pub\"" | base64 -d) + + sh -c "echo $CERTIFICATE > ${SYSTEM_SSH_DIR}/ssh_host_rsa_key-cert.pub" + echo "Certificate written to ${SYSTEM_SSH_DIR}/ssh_host_rsa_key-cert.pub" + + test -f ${SYSTEM_SSH_DIR}/user_ca.pub || echo $USER_CA_PUBKEY > ${SYSTEM_SSH_DIR}/user_ca.pub + + if [[ $(grep -q "HostCertificate" "${SYSTEM_SSH_DIR}/sshd_config"; echo $?) -ne 0 ]]; then + echo "HostCertificate ${SYSTEM_SSH_DIR}/ssh_host_rsa_key-cert.pub" >> ${SYSTEM_SSH_DIR}/sshd_config + fi + + if [[ $(grep -q "TrustedUserCAKeys" "${SYSTEM_SSH_DIR}/sshd_config"; echo $?) -ne 0 ]]; then + echo "TrustedUserCAKeys ${SYSTEM_SSH_DIR}/user_ca.pub" >> ${SYSTEM_SSH_DIR}/sshd_config + fi +elif [[ $CA_ACTION = "generateClientX509Cert" ]]; then + ENCODED_CERTIFICATE=$(curl "${CA_LAMBDA_URL}" -H 'content-type: application/json' -d "$EVENT_JSON" | tr -d '"') + CERTIFICATE=$(echo $ENCODED_CERTIFICATE | base64 -d) + sh -c "echo '$CERTIFICATE' > ${SYSTEM_SSL_DIR}/privateCA/public.crt" + echo "Certificate written to ${SYSTEM_SSL_DIR}/privateCA/public.crt" +fi diff --git a/private-ca/client/Docker/run.sh b/private-ca/client/Docker/run.sh new file mode 100644 index 0000000..48261b9 --- /dev/null +++ b/private-ca/client/Docker/run.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "0 0 */1 * * cd /app && generate-certificate-curl.sh generateHostSSHCert ${CA_LAMBDA_URL} ${USER_SSH_DIR} ${SYSTEM_SSH_DIR} ${SYSTEM_SSL_DIR} ${AWS_STS_REGION} ${AWS_PROFILE} > /dev/stdout" > crontab.txt +/usr/bin/crontab crontab.txt +/usr/sbin/crond -f -l 8 diff --git a/private-ca/client/generate-certificate-aws-cli.sh b/private-ca/client/generate-certificate-aws-cli.sh new file mode 100755 index 0000000..2a2a72d --- /dev/null +++ b/private-ca/client/generate-certificate-aws-cli.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +CA_LAMBDA_FUNCTION_NAME="privateCA" + +# Edit values here +###################################################### +# CA_ACTION="getHostSSHCert" +# CA_ACTION="getClientSSHCert" +CA_ACTION="generateRootX509Cert" +# CA_ACTION="generateClientX509Cert" + +# # Get client SSL certificate +# SSL_ATTRS_VALIDITY="" +# SSL_CLIENT_PUBKEY_PEM="" + +# # Get host SSH certificate +# SSH_ATTRS_VALIDITY="" +# SSH_HOST_RSA_PUBKEY="" + +# # Get client SSH certificate +# SSH_ATTRS_VALIDITY="" +# SSH_CLIENT_RSA_PUBKEY="" +###################################################### + +# Temporary Credentials +TEMP_CREDS=$(aws sts get-session-token) + +ACCESS_KEY_ID=$(echo $TEMP_CREDS | jq -r ".Credentials.AccessKeyId") +SECRET_ACCESS_KEY=$(echo $TEMP_CREDS | jq -r ".Credentials.SecretAccessKey") +SESSION_TOKEN=$(echo $TEMP_CREDS | jq -r ".Credentials.SessionToken") + +PYTHON_EXEC=$(which python || which python3) +LAMBDA_REGION=${AWS_REGION:-'ap-south-1'} + +# Auth Headers +$PYTHON_EXEC -m venv env && source env/bin/activate +pip install boto3 + +output=$($PYTHON_EXEC auth-header.py $ACCESS_KEY_ID $SECRET_ACCESS_KEY $SESSION_TOKEN) +auth_header=$(echo $output | jq -r ".Authorization") +date=$(echo $output | jq -r ".Date") + +echo "{\"auth\": { + \"amzDate\": \"${date}\", + \"authorizationHeader\": \"${auth_header}\", + \"sessionToken\": \"${SESSION_TOKEN}\" + }, + \"sslAttrs\": { + \"validityPeriod\": \"${SSL_ATTRS_VALIDITY}\", + \"clientPublicKeyPem\": \"${SSL_CLIENT_PUBKEY_PEM}\" + }, + \"sshAttrs\": { + \"validity\": \"${SSH_ATTRS_VALIDITY}\", + \"sshHostRSAKey\": \"${SSH_HOST_RSA_PUBKEY}\", + \"sshClientRSAKey\": \"${SSH_CLIENT_RSA_PUBKEY}\" + }, + \"action\": \"${CA_ACTION}\" + }" > event.json + + +aws lambda invoke --function-name ${CA_LAMBDA_FUNCTION_NAME} --cli-binary-format raw-in-base64-out --payload file://event.json response.json --region $LAMBDA_REGION +response_body=$(cat response.json | jq -r ".body" | tr -d '"' | sed 's/\\r\\n/ \ +/g') + +echo ${response_body} + +# Clean up +deactivate +sudo rm -r env *.json diff --git a/private-ca/client/generate-certificate-curl.sh b/private-ca/client/generate-certificate-curl.sh new file mode 100755 index 0000000..3c1d32a --- /dev/null +++ b/private-ca/client/generate-certificate-curl.sh @@ -0,0 +1,155 @@ +#!/bin/bash + +CA_ACTION=${1} +CA_LAMBDA_URL=${2} +USER_SSH_DIR=${3:-"/home/$USER/.ssh"} +SYSTEM_SSH_DIR=${4:-"/etc/ssh"} +SYSTEM_SSL_DIR=${5:-"/etc/ssl"} +AWS_STS_REGION=${6:-"ap-southeast-1"} +AWS_PROFILE=${7:-"default"} + +PYTHON_EXEC=$(which python || which python3) + +# Check for options +while getopts ":h" option; do + case $option in + h) + echo "Usage: ./generate-certificate.sh [ACTION] [PUBLIC KEY FILE] [LAMBDA URL]" + echo "Possible actions:" + echo " generateHostSSHCert: Generates SSH Certificate for Host" + echo " generateClientSSHCert: Generates SSH Certificate for Client" + echo " generateClientX509Cert: Generates X.509 Certificate for Client" + exit;; + *) + echo "Error: Invalid option" + exit;; + esac +done + +# Check for CA Action +if [[ $CA_ACTION = "generateClientSSHCert" ]]; then + if test -f ${USER_SSH_DIR}/id_rsa-cert.pub; then + # Client SSH Certificate already exists + current_timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S") + certificate_expiration_timestamp=$(ssh-keygen -Lf ${USER_SSH_DIR}/id_rsa-cert.pub | awk '/Valid:/{print $NF}') + + if [[ $certificate_expiration_timestamp > $current_timestamp ]]; then + # Certificate is valid + echo "A valid certificate was found at ${USER_SSH_DIR}/id_rsa-cert.pub." + echo "Aborting..." + exit; + else + # Certificate expired + rm ${USER_SSH_DIR}/id_rsa-cert.pub + fi + fi + test -f ${USER_SSH_DIR}/id_rsa.pub || ssh-keygen -t rsa -b 4096 -f ${USER_SSH_DIR}/id_rsa -C host_ca -N "" + CERT_PUBKEY=$(cat ${USER_SSH_DIR}/id_rsa.pub | base64 | tr -d \\n) + +elif [[ $CA_ACTION = "generateHostSSHCert" ]]; then + if test -f ${SYSTEM_SSH_DIR}/ssh_host_rsa_key-cert.pub; then + # Host SSH Certificate already exists + current_timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S") + certificate_expiration_timestamp=$(ssh-keygen -Lf ${SYSTEM_SSH_DIR}/ssh_host_rsa_key-cert.pub | awk '/Valid:/{print $NF}') + + if [[ $certificate_expiration_timestamp_seconds > $current_timestamp ]]; then + # Certificate is valid + echo "A valid certificate was found at ${SYSTEM_SSH_DIR}/ssh_host_rsa_key-cert.pub." + echo "Aborting..." + exit; + else + # Certificate expired + rm ${SYSTEM_SSH_DIR}/ssh_host_rsa_key-cert.pub + fi + fi + test -f ${SYSTEM_SSH_DIR}/ssh_host_rsa_key.pub || sudo ssh-keygen -t rsa -b 4096 -f ${SYSTEM_SSH_DIR}/ssh_host_rsa_key -C host_ca -N "" + CERT_PUBKEY=$(cat ${SYSTEM_SSH_DIR}/ssh_host_rsa_key.pub | base64 | tr -d \\n) + +elif [[ $CA_ACTION = "generateClientX509Cert" ]]; then + test -d ${SYSTEM_SSL_DIR}/privateCA || sudo mkdir -p ${SYSTEM_SSL_DIR}/privateCA + if test -f ${SYSTEM_SSL_DIR}/privateCA/public.crt; then + # X.509 Certificate already exists + + if ( sudo openssl x509 -checkend 300 -noout -in ${SYSTEM_SSL_DIR}/privateCA/public.crt ); then + # Certificate is valid for atleast 300 seconds (5 minutes) + echo "A valid certificate was found at ${SYSTEM_SSL_DIR}/privateCA/public.crt." + echo "Aborting..." + exit; + else + # Certificate expired or about to expire + sudo rm ${SYSTEM_SSL_DIR}/privateCA/public.crt + fi + fi + if ! test -f ${SYSTEM_SSL_DIR}/privateCA/public.pem; then + sudo openssl genrsa -out ${SYSTEM_SSL_DIR}/privateCA/key.pem 2048 + sudo openssl rsa -in ${SYSTEM_SSL_DIR}/privateCA/key.pem -outform PEM -pubout -out ${SYSTEM_SSL_DIR}/privateCA/public.pem + fi + CERT_PUBKEY=$(cat ${SYSTEM_SSL_DIR}/privateCA/public.pem | base64 | tr -d \\n) +else + echo "Invalid Action" + echo "Possible actions include:" + echo " generateHostSSHCert: Generates SSH Certificate for Host" + echo " generateClientSSHCert: Generates SSH Certificate for Client" + echo " generateClientX509Cert: Generates X.509 Certificate for Client" + exit; +fi + +# Temporary Credentials +TEMP_CREDS=$(aws sts get-session-token --region $AWS_STS_REGION --profile $AWS_PROFILE) + +ACCESS_KEY_ID=$(echo $TEMP_CREDS | jq -r ".Credentials.AccessKeyId") +SECRET_ACCESS_KEY=$(echo $TEMP_CREDS | jq -r ".Credentials.SecretAccessKey") +SESSION_TOKEN=$(echo $TEMP_CREDS | jq -r ".Credentials.SessionToken") + +# Auth Headers +$PYTHON_EXEC -m venv env && source env/bin/activate +pip install boto3 + +output=$($PYTHON_EXEC aws-auth-header.py $ACCESS_KEY_ID $SECRET_ACCESS_KEY $SESSION_TOKEN $AWS_STS_REGION) +auth_header=$(echo $output | jq -r ".Authorization") +date=$(echo $output | jq -r ".Date") + +EVENT_JSON=$(echo "{\"auth\":{\"amzDate\":\"${date}\",\"authorizationHeader\":\"${auth_header}\",\"sessionToken\":\"${SESSION_TOKEN}\"},\"certPubkey\":\"${CERT_PUBKEY}\",\"action\":\"${CA_ACTION}\",\"awsSTSRegion\":\"${AWS_STS_REGION}\"}") + + +if [[ $CA_ACTION = "generateClientSSHCert" ]]; then + LAMBDA_RESPONSE=$(curl "${CA_LAMBDA_URL}" -H 'content-type: application/json' -d "$EVENT_JSON") + ENCODED_CERTIFICATE=$(echo $LAMBDA_RESPONSE | jq -r ".certificate") + CERTIFICATE=$(echo $ENCODED_CERTIFICATE | base64 -d) + HOST_CA_PUBKEY=$(echo $LAMBDA_RESPONSE | jq -r ".\"host_ca.pub\"" | base64 -d) + + echo $CERTIFICATE > ${USER_SSH_DIR}/id_rsa-cert.pub + echo "Certificate written to ${USER_SSH_DIR}/id_rsa-cert.pub" + + if [[ $(grep -q "@cert-authority" "${USER_SSH_DIR}/known_hosts"; echo $?) -ne 0 ]]; then + echo "@cert-authority * ${HOST_CA_PUBKEY}" >> ${USER_SSH_DIR}/known_hosts + fi + +elif [[ $CA_ACTION = "generateHostSSHCert" ]]; then + LAMBDA_RESPONSE=$(curl "${CA_LAMBDA_URL}" -H 'content-type: application/json' -d "$EVENT_JSON") + ENCODED_CERTIFICATE=$(echo $LAMBDA_RESPONSE | jq -r ".certificate") + CERTIFICATE=$(echo $ENCODED_CERTIFICATE | base64 -d) + USER_CA_PUBKEY=$(echo $LAMBDA_RESPONSE | jq -r ".\"user_ca.pub\"" | base64 -d) + + sudo sh -c "echo $CERTIFICATE > ${SYSTEM_SSH_DIR}/ssh_host_rsa_key-cert.pub" + echo "Certificate written to ${SYSTEM_SSH_DIR}/ssh_host_rsa_key-cert.pub" + + test -f ${SYSTEM_SSH_DIR}/user_ca.pub || echo $USER_CA_PUBKEY > ${SYSTEM_SSH_DIR}/user_ca.pub + + if [[ $(grep -q "HostCertificate" "${SYSTEM_SSH_DIR}/sshd_config"; echo $?) -ne 0 ]]; then + echo "HostCertificate ${SYSTEM_SSH_DIR}/ssh_host_rsa_key-cert.pub" >> ${SYSTEM_SSH_DIR}/sshd_config + fi + + if [[ $(grep -q "TrustedUserCAKeys" "${SYSTEM_SSH_DIR}/sshd_config"; echo $?) -ne 0 ]]; then + echo "TrustedUserCAKeys ${SYSTEM_SSH_DIR}/user_ca.pub" >> ${SYSTEM_SSH_DIR}/sshd_config + fi +elif [[ $CA_ACTION = "generateClientX509Cert" ]]; then + ENCODED_CERTIFICATE=$(curl "${CA_LAMBDA_URL}" -H 'content-type: application/json' -d "$EVENT_JSON" | tr -d '"') + CERTIFICATE=$(echo $ENCODED_CERTIFICATE | base64 -d) + sudo sh -c "echo '$CERTIFICATE' > ${SYSTEM_SSL_DIR}/privateCA/public.crt" + echo "Certificate written to ${SYSTEM_SSL_DIR}/privateCA/public.crt" +fi + +# Clean up +deactivate +rm -r env/ \ No newline at end of file diff --git a/private-ca/deploy-server-on-lambda.sh b/private-ca/deploy-server-on-lambda.sh new file mode 100755 index 0000000..23b87dc --- /dev/null +++ b/private-ca/deploy-server-on-lambda.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +SECRET_NAME=${1:-"privateCA"} +ROLE_NAME=${2:-"privateCALambdaRole"} +POLICY_NAME=${3:-"PrivateCAPolicy"} +LAYER_NAME=${4:-"openssh"} +FUNCTION_NAME=${5:-"privateCA"} +AWS_REGION=${6:-"ap-southeast-1"} +AWS_PROFILE=${7:-"default"} +################## Secret ################## + +# Generate Keys +ssh-keygen -t rsa -b 4096 -f host_ca -C host_ca -N "" +ssh-keygen -t rsa -b 4096 -f user_ca -C user_ca -N "" + +openssl genrsa -out key.pem 2048 +openssl rsa -in key.pem -outform PEM -pubout -out public.pem +openssl req -new -x509 -key key.pem -out root.crt -days 365 -subj "/C=US/ST=California/L=YourCity/O=Fundwave/OU=Fundwave/CN=FundwaveCA" + +HOST_CA_PRIVATE_KEY=$(cat host_ca | base64 | tr -d \\n) +HOST_CA_PUBLIC_KEY=$(cat host_ca.pub | base64 | tr -d \\n) +USER_CA_PRIVATE_KEY=$(cat user_ca | base64 | tr -d \\n) +USER_CA_PUBLIC_KEY=$(cat user_ca.pub | base64 | tr -d \\n) +ROOT_SSL_PRIVATE_KEY=$(cat key.pem | base64 | tr -d \\n) +ROOT_SSL_PUBLIC_KEY=$(cat public.pem | base64 | tr -d \\n) +ROOT_SSL_CERT=$(cat root.crt | base64 | tr -d \\n) + +echo "{\"host_ca\": \"${HOST_CA_PRIVATE_KEY}\", \"host_ca.pub\": \"${HOST_CA_PUBLIC_KEY}\", \"user_ca\": \"${USER_CA_PRIVATE_KEY}\",\"user_ca.pub\": \"${USER_CA_PUBLIC_KEY}\",\"root_ssl_private_key\": \"${ROOT_SSL_PRIVATE_KEY}\",\"root_ssl_public_key\": \"${ROOT_SSL_PUBLIC_KEY}\", \"rootX509cert\": \"${ROOT_SSL_CERT}\"}" | jq . > secret.json + +# Create Secret +SECRET_ARN=$(aws secretsmanager create-secret \ + --name $SECRET_NAME \ + --secret-string file://secret.json \ + --region $AWS_REGION \ + --profile $AWS_PROFILE \ + | jq ".ARN" | tr -d '"') + +# Clean up +rm host_ca host_ca.pub user_ca user_ca.pub key.pem public.pem root.crt secret.json +############################################ + +################### Role ################### + +# Create role for lambda +echo "{\"Version\": \"2012-10-17\",\"Statement\": [{\"Sid\": \"AllowLambdaAssumeRole\",\"Effect\": \"Allow\",\"Principal\": {\"Service\": \"lambda.amazonaws.com\"},\"Action\": \"sts:AssumeRole\"}]}" | jq . > Trust-Policy.json + +ROLE_ARN=$(aws iam create-role \ + --role-name $ROLE_NAME \ + --region $AWS_REGION \ + --profile $AWS_PROFILE \ + --assume-role-policy-document file://Trust-Policy.json | jq ".Role.Arn" | tr -d '"') + +# Create Policy for Lambda Role to Read and Update Secrets +echo "{\"Version\": \"2012-10-17\",\"Statement\": [{\"Sid\": \"VisualEditor0\",\"Effect\": \"Allow\",\"Action\": [\"secretsmanager:GetSecretValue\"],\"Resource\": \"${SECRET_ARN}\"}, {\"Action\": [\"logs:CreateLogGroup\",\"logs:CreateLogStream\",\"logs:PutLogEvents\"],\"Effect\": \"Allow\",\"Resource\": \"arn:aws:logs:*:*:*\"}]}" | jq . > Policy.json + +POLICY_ARN=$(aws iam create-policy --policy-name $POLICY_NAME --region $AWS_REGION --profile $AWS_PROFILE --policy-document file://Policy.json | jq ".Policy.Arn" | tr -d '"') + +# Attach policy to role +aws iam attach-role-policy --role-name $ROLE_NAME --policy-arn $POLICY_ARN --region $AWS_REGION --profile $AWS_PROFILE + +# Clean up +rm Trust-Policy.json Policy.json +############################################ + +################### Layer ################## + +# Create OpenSSH layer +sudo docker run --rm -v $(pwd)/openssh-layer:/lambda/opt lambci/yumda:2 yum install -y openssh +cd openssh-layer +sudo zip -yr ./openssh-layer.zip . > /dev/null +LAYER_ARN=$(aws lambda publish-layer-version \ + --layer-name $LAYER_NAME \ + --zip-file fileb://openssh-layer.zip \ + --region $AWS_REGION \ + --profile $AWS_PROFILE \ + --query 'LayerVersionArn' \ + --output text) +cd .. + +# Clean up +sudo rm -r openssh-layer/ +############################################ + +################## Lambda ################## + +# Create lambda function +cd server +npm i +zip -r ./lambda.zip . +mv lambda.zip ../ +cd .. + +aws lambda create-function \ + --function-name $FUNCTION_NAME \ + --runtime nodejs18.x \ + --region $AWS_REGION \ + --profile $AWS_PROFILE \ + --handler index_lambda.handler \ + --zip-file fileb://lambda.zip \ + --layers $LAYER_ARN \ + --role $ROLE_ARN + +aws lambda add-permission \ + --function-name $FUNCTION_NAME \ + --action lambda:InvokeFunctionUrl \ + --principal "*" \ + --function-url-auth-type "NONE" \ + --statement-id url \ + --region $AWS_REGION \ + --profile $AWS_PROFILE + +FUNCTION_URL=$(aws lambda create-function-url-config --function-name "privateCA" --auth-type "NONE" --region $AWS_REGION --profile $AWS_PROFILE | jq -r ".FunctionUrl") + +echo "CA deployed at URL:" +echo "${FUNCTION_URL}" + +# Clean up +rm -r server/node_modules/ server/package-lock.json lambda.zip +########################################### \ No newline at end of file diff --git a/private-ca/server/generate-client-ssh-cert.js b/private-ca/server/generate-client-ssh-cert.js new file mode 100644 index 0000000..06f5d0d --- /dev/null +++ b/private-ca/server/generate-client-ssh-cert.js @@ -0,0 +1,38 @@ +import fs from 'fs'; +import child_process from 'child_process'; +import util from 'util'; + +const exec = util.promisify(child_process.exec); +const validityInDays = process.env.validityInDays ?? 1; +const caKeyPath = "/tmp/client_ca"; +const publicKeyName = "ssh_client_rsa_key"; +const publicKeyPath = "/tmp/" + publicKeyName + ".pub"; +const certificatePath = "/tmp/" + publicKeyName + "-cert.pub"; + +export const signClientSSHCertificate = async (callerIdentity, secret, certPubkey) => { + + const arn = callerIdentity.GetCallerIdentityResponse.GetCallerIdentityResult.Arn; + const roleName = arn.match(/\/([^/]+)$/)?.[1]; + const user_ca = Buffer.from(secret.user_ca, 'base64').toString('utf-8'); + + certPubkey = Buffer.from(certPubkey, 'base64').toString('utf-8'); + fs.writeFileSync(caKeyPath, user_ca); + fs.writeFileSync(publicKeyPath, certPubkey); + + let { stdout, stderr } = await exec(`chmod 600 ${caKeyPath}`); + console.log('stdout:', stdout); + console.log('stderr:', stderr); + + ( + { stdout, stderr } = await exec( + `ssh-keygen -s ${caKeyPath} -t rsa-sha2-512 -I client_${roleName} -n ${roleName} -V +${validityInDays}d ${publicKeyPath}` + ) + ); + + console.log('stdout:', stdout); + console.log('stderr:', stderr); + + const certificate = fs.readFileSync(certificatePath, 'utf8'); + return certificate; + +}; diff --git a/private-ca/server/generate-client-x509-cert.js b/private-ca/server/generate-client-x509-cert.js new file mode 100644 index 0000000..545291f --- /dev/null +++ b/private-ca/server/generate-client-x509-cert.js @@ -0,0 +1,79 @@ +import forge from 'node-forge'; +import md from 'node-forge'; +import crypto from "crypto"; + +const countryName = process.env.countryName ?? "SG"; +const localityName = process.env.localityName ?? "Singapore"; +const organizationName = process.env.organizationName ?? "Fundwave"; +const organizationalUnitName = process.env.localityName ?? "Fundwave"; + +const validityInDays = process.env.validityInDays ?? 1; +const messageDigestAlg = process.env.messageDigestAlg ?? "sha256"; + +export const generateClientX509Cert = async (callerIdentity, secret, event) => { + + const pki = forge.pki; + + const arn = callerIdentity.GetCallerIdentityResponse.GetCallerIdentityResult.Arn; + const roleName = arn.match(/\/([^/]+)$/)?.[1]; + + // Load the root certificate private key from a file or string + const rootKeyPem = Buffer.from(secret.root_ssl_private_key, 'base64').toString('utf-8'); + const rootKey = pki.privateKeyFromPem(rootKeyPem); + + // Load the root certificate public key from a file or string + let rootCertKey = 'rootX509cert'; + if(!(rootCertKey in secret)) + { + console.log("No root certificate found. Aborting creation of client X.509 certificate."); + return { + statusCode: 500, + body: "No root certificate found." + }; + } + let rootCertPem = Buffer.from(secret['rootX509cert'], 'base64').toString('utf-8'); + const rootCert = pki.certificateFromPem(rootCertPem); + + // openssl genrsa -out key.pem 2048 + // openssl rsa -in key.pem -outform PEM -pubout -out public.pem + const clientPublicKey = pki.publicKeyFromPem(Buffer.from(event.certPubkey, 'base64').toString('utf-8')); + + // Create a client certificate signing request (CSR) + const clientCertReq = pki.createCertificationRequest(); + clientCertReq.publicKey = clientPublicKey; + clientCertReq.setSubject([ + { name: 'countryName', value: countryName }, + { name: 'localityName', value: localityName }, + { name: 'organizationName', value: organizationName }, + { name: 'organizationalUnitName', value: organizationalUnitName }, + { name: 'commonName', value: roleName } + ]); + + // Sign the client certificate request with the root certificate and private key + const clientCert = pki.createCertificate(); + clientCert.publicKey = clientCertReq.publicKey; + // clientCert.serialNumber = crypto.randomBytes(8).toString("hex"); // Set a unique serial number upto 20 bytes. https://security.stackexchange.com/questions/35691/what-is-the-difference-between-serial-number-and-thumbprint https://www.hindawi.com/journals/scn/2019/6013846/ + + clientCert.setSubject(clientCertReq.subject.attributes); + clientCert.setIssuer(rootCert.subject.attributes); + clientCert.setExtensions([ + { name: 'basicConstraints', cA: false }, + { name: 'keyUsage', digitalSignature: true, nonRepudiation: true, keyEncipherment: true }, + ]); + + const startDate = new Date(); // Valid from the current date and time + const endDate = new Date(); + endDate.setDate(startDate.getDate() + validityInDays); + clientCert.validity.notBefore = startDate; + clientCert.validity.notAfter = endDate; + + clientCert.sign(rootKey, md[messageDigestAlg].create()); + + // Convert the signed client certificate to PEM format + const clientCertPem = pki.certificateToPem(clientCert); + return { + statusCode: 200, + body: Buffer.from(clientCertPem).toString('base64') + }; + +} \ No newline at end of file diff --git a/private-ca/server/generate-host-ssh-cert.js b/private-ca/server/generate-host-ssh-cert.js new file mode 100644 index 0000000..eedfccd --- /dev/null +++ b/private-ca/server/generate-host-ssh-cert.js @@ -0,0 +1,31 @@ +import fs from 'fs'; +import child_process from 'child_process'; +import util from 'util'; + +const exec = util.promisify(child_process.exec); + +export const signHostSSHCertificate = async (callerIdentity, secret, certPubkey) => { + + const arn = callerIdentity.GetCallerIdentityResponse.GetCallerIdentityResult.Arn; + const roleName = arn.match(/\/([^/]+)$/)?.[1]; + + const caKeyPath = "/tmp/host_ca"; + const publicKeyName = "ssh_host_rsa_key"; + const publicKeyPath = "/tmp/" + publicKeyName + ".pub"; + const certificatePath = "/tmp/" + publicKeyName + "-cert.pub"; + const host_ca = Buffer.from(secret.host_ca, 'base64').toString('utf-8'); + certPubkey = Buffer.from(certPubkey, 'base64').toString('utf-8'); + fs.writeFileSync(caKeyPath, host_ca); + fs.writeFileSync(publicKeyPath, certPubkey); + + let { stdout, stderr } = await exec(`chmod 600 ${caKeyPath}`); + console.log('stdout:', stdout); + console.log('stderr:', stderr); + + ({ stdout, stderr } = await exec(`ssh-keygen -s ${caKeyPath} -t rsa-sha2-512 -I host_${roleName} -h -n ${roleName} -V +1d ${publicKeyPath}`)); + console.log('stdout:', stdout); + console.log('stderr:', stderr); + + const certificate = fs.readFileSync(certificatePath, 'utf8'); + return certificate; +}; diff --git a/private-ca/server/get-caller-identity.js b/private-ca/server/get-caller-identity.js new file mode 100644 index 0000000..18aa180 --- /dev/null +++ b/private-ca/server/get-caller-identity.js @@ -0,0 +1,54 @@ +import https from 'https'; + +export const getCallerIdentity = (event) => { + + const auth = event.auth; + const region = event.awsSTSRegion; + const host = 'sts.' + region + '.amazonaws.com'; + const path = '/'; + const payload = 'Action=GetCallerIdentity&Version=2011-06-15'; + + // Set the headers + const headers = { + 'accept': 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded', + 'X-Amz-Date': auth.amzDate, + 'Authorization': auth.authorizationHeader, + 'X-Amz-Security-Token': auth.sessionToken, + 'Aud': 'FundwaveCA' + }; + + const options = { + hostname: host, + path: path, + method: 'POST', + headers: headers + }; + + return new Promise((resolve, reject) => { + + const req = https.request(options, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + try { + resolve(JSON.parse(data)); + } catch (err) { + reject(new Error(err)); + } + }); + }); + + req.on('error', (error) => { + reject(new Error(error)); + }); + + req.write(payload); + req.end(); + }); + +}; \ No newline at end of file diff --git a/private-ca/server/index_lambda.js b/private-ca/server/index_lambda.js new file mode 100644 index 0000000..80af176 --- /dev/null +++ b/private-ca/server/index_lambda.js @@ -0,0 +1,41 @@ +import { signHostSSHCertificate } from './generate-host-ssh-cert.js'; +import { signClientSSHCertificate } from './generate-client-ssh-cert.js'; +import { getCallerIdentity } from './get-caller-identity.js'; +import { generateClientX509Cert } from './generate-client-x509-cert.js'; +import { getSecret } from './secret-manager-utils.js'; +const AWS_SCRTS_REGION = process.env.AWS_SCRTS_REGION; + +export const handler = async (event) => { + + event=JSON.parse(event.body); + + // auth + const callerIdentity = await getCallerIdentity(event); + + // secret + const secret = await getSecret(AWS_SCRTS_REGION, 'privateCA'); + + // action + switch(event.action) { + case "generateHostSSHCert": + const hostSSHCert = await signHostSSHCertificate(callerIdentity, secret, event.certPubkey); + return { + statusCode: 200, + body: "{\"certificate\" : \""+Buffer.from(hostSSHCert).toString('base64')+"\", \"user_ca.pub\": \""+secret["user_ca.pub"]+"\"}" + }; + case "generateClientSSHCert": + const clientSSHCert = await signClientSSHCertificate(callerIdentity, secret, event.certPubkey); + return { + statusCode: 200, + body: "{\"certificate\" : \""+Buffer.from(clientSSHCert).toString('base64')+"\", \"host_ca.pub\": \""+secret["host_ca.pub"]+"\"}" + }; + case "generateClientX509Cert": + return await generateClientX509Cert(callerIdentity, secret, event); + default: + console.log("Invalid Action") + return { + statusCode: 400, + body: JSON.stringify('Invalid Action') + }; + } +}; diff --git a/private-ca/server/package-lock.json b/private-ca/server/package-lock.json new file mode 100644 index 0000000..3600019 --- /dev/null +++ b/private-ca/server/package-lock.json @@ -0,0 +1,596 @@ +{ + "name": "private-ca", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "private-ca", + "version": "0.0.1", + "license": "ISC", + "dependencies": { + "aws-sdk": "^2.1398.0", + "node-forge": "^1.3.1" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-sdk": { + "version": "2.1414.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1414.0.tgz", + "integrity": "sha512-WhqTWiTZRUxWITvUG5VMPYGdCLNAm4zOTDIiotbErR9x+uDExk2CAGbXE8HH11+tD8PhZVXyukymSiG+7rJMMg==", + "dependencies": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "util": "^0.12.4", + "uuid": "8.0.0", + "xml2js": "0.5.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" + }, + "node_modules/url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/uuid": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", + "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.10.tgz", + "integrity": "sha512-uxoA5vLUfRPdjCuJ1h5LlYdmTLbYfums398v3WLkM+i/Wltl2/XyZpQWKbN++ck5L64SR/grOHqtXCUKmlZPNA==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + } + }, + "dependencies": { + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + }, + "aws-sdk": { + "version": "2.1414.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1414.0.tgz", + "integrity": "sha512-WhqTWiTZRUxWITvUG5VMPYGdCLNAm4zOTDIiotbErR9x+uDExk2CAGbXE8HH11+tD8PhZVXyukymSiG+7rJMMg==", + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "util": "^0.12.4", + "uuid": "8.0.0", + "xml2js": "0.5.0" + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==" + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + }, + "is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==" + }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==" + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "requires": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "uuid": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", + "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==" + }, + "which-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.10.tgz", + "integrity": "sha512-uxoA5vLUfRPdjCuJ1h5LlYdmTLbYfums398v3WLkM+i/Wltl2/XyZpQWKbN++ck5L64SR/grOHqtXCUKmlZPNA==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, + "xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + } + } +} diff --git a/private-ca/server/package.json b/private-ca/server/package.json new file mode 100644 index 0000000..fa1d9a5 --- /dev/null +++ b/private-ca/server/package.json @@ -0,0 +1,17 @@ +{ + "name": "private-ca", + "version": "0.0.1", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "start": "node index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "aws-sdk": "^2.1398.0", + "node-forge": "^1.3.1" + } +} diff --git a/private-ca/server/secret-manager-utils.js b/private-ca/server/secret-manager-utils.js new file mode 100644 index 0000000..1a622fd --- /dev/null +++ b/private-ca/server/secret-manager-utils.js @@ -0,0 +1,22 @@ +import aws from "aws-sdk"; + +export const getSecret = async (secretRegion, secretId) => { + var secretsmanager = new aws.SecretsManager({ region: secretRegion }); + const secretString = await secretsmanager.getSecretValue({ SecretId: secretId }).promise(); + const secret = JSON.parse(secretString.SecretString); + return secret; +}; + +/** +export const updateSecret = async (secretRegion, secretId, key, value) => { + var secretsmanager = new aws.SecretsManager({ region: secretRegion }); + let secret = await getSecret(secretId); + secret[key] = value; + var params = { + SecretId: secretId, + SecretString: JSON.stringify(secret) + }; + const updateRes = await secretsmanager.updateSecret(params).promise(); + return updateRes; +} +**/ diff --git a/private-ca/update-server-on-lambda.sh b/private-ca/update-server-on-lambda.sh new file mode 100755 index 0000000..7bc60ca --- /dev/null +++ b/private-ca/update-server-on-lambda.sh @@ -0,0 +1,15 @@ +FUNCTION_NAME=${1:-'privateCA'} +REGION=${2:-'ap-southeast-1'} +PROFILE=${3:-'default'} + +cd server +npm i +zip -r ./lambda.zip . +mv lambda.zip ../ +cd .. + +aws lambda update-function-code \ + --function-name $FUNCTION_NAME \ + --zip-file fileb://lambda.zip --region $REGION --profile $PROFILE 1>/dev/null 2>/dev/stderr + +rm -r lambda.zip \ No newline at end of file