Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
798e263
Add files for private CA
sudo-rgorai Jun 22, 2023
84a9e1d
Take AWS region as input
sudo-rgorai Jun 23, 2023
1e8aa25
Remove action for generating root X.509 certificate
sudo-rgorai Jun 24, 2023
123b832
Use current timestamp for auth header
sudo-rgorai Jun 24, 2023
ce72f23
Accept lambda event as params
sudo-rgorai Jun 24, 2023
571e245
Use region for all aws services
sudo-rgorai Jun 26, 2023
abcbce9
Use exact pattern matching for deleting temporary files
sudo-rgorai Jun 26, 2023
af7756d
Fix filenames in deploy-resources.sh
sudo-rgorai Jun 26, 2023
2e1b9b7
Change CA action from get to generate
sudo-rgorai Jun 26, 2023
8039e58
Delete secret.json after use
sudo-rgorai Jun 26, 2023
ba4deb7
Delete package-lock.json
sudo-rgorai Jun 26, 2023
e27dff0
Fix ssh client cert validity
sudo-rgorai Jun 26, 2023
c30758f
Use pubkey file instead of string for generating certificate
sudo-rgorai Jun 26, 2023
99afb54
Do not use response.json
sudo-rgorai Jun 26, 2023
eb67f93
Base64 encode response certificate
sudo-rgorai Jul 4, 2023
99a8b9b
Invoke function using lambda URL instead of AWS CLI
sudo-rgorai Jul 4, 2023
b9c2c48
Add -h help tag and handle invalid action
sudo-rgorai Jul 6, 2023
d38a12c
Certificate validity should be decided by CA not by the subject
sudo-rgorai Jul 6, 2023
1b7802e
Check for existing certificates and their expiration
sudo-rgorai Jul 6, 2023
4c70c44
Add certificate expiration buffer of 5 minutes
sudo-rgorai Jul 7, 2023
6efcdb3
Use awscurl instead of curl to generate certs
sudo-rgorai Jul 11, 2023
ff72a42
Do not use awscurl
sudo-rgorai Jul 11, 2023
c854587
reorganize directories
Jul 11, 2023
d67a780
add awsprofile, let server decide it's own secret region
Jul 11, 2023
e073fbc
Add audience header
sudo-rgorai Jul 12, 2023
b4f21c5
Use docker container
sudo-rgorai Jul 12, 2023
b078b61
Fix server deployment issues
sudo-rgorai Jul 13, 2023
6424640
Add instruction for AWS secrets region environment variable
sudo-rgorai Jul 14, 2023
a128e39
Specify certificate type
sudo-rgorai Jul 14, 2023
d4fdede
private-ca: use variables for validity, cert details
Aug 4, 2023
28b4de3
merge main
Aug 4, 2023
2720292
Added cron job to Docker Container to regenerate certs everyday
Sep 25, 2023
23f4391
added override default cert location for curl
Sep 25, 2023
312cda7
workflow to push private ca container to docker hub
Oct 6, 2023
9df7571
updated push path for privateCA workflow
Oct 13, 2023
dfa428c
fix: set curl cert bundle path
Oct 26, 2023
315bc95
fix: changed cd path [skip ci]
Oct 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/build-private-ca-on-push.yaml
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/node_modules/**
2 changes: 1 addition & 1 deletion .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ threshold: medium
fileignoreconfig:
- filename: aws-credentials-utils/store-credentials.sh
checksum: 784aec6e80314be796af73887ba630fbaf0e116cc4d11bd5cba787a20f9c4bbb
version: ""
version: ""
43 changes: 43 additions & 0 deletions private-ca/README.md
Original file line number Diff line number Diff line change
@@ -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"
15 changes: 15 additions & 0 deletions private-ca/client/Docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -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" ]
63 changes: 63 additions & 0 deletions private-ca/client/Docker/README.md
Original file line number Diff line number Diff line change
@@ -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=<action> \
-e CA_LAMBDA_URL=<lambda_url> \
-e USER_SSH_DIR=<user_ssh_directory> \
-e SYSTEM_SSH_DIR=<system_ssh_directory> \
-e SYSTEM_SSL_DIR=<system_ssl_directory> \
-e AWS_STS_REGION=<sts_region> \
-e AWS_PROFILE=<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.
30 changes: 30 additions & 0 deletions private-ca/client/Docker/aws-auth-header.py
Original file line number Diff line number Diff line change
@@ -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)
148 changes: 148 additions & 0 deletions private-ca/client/Docker/generate-certificate-curl.sh
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions private-ca/client/Docker/run.sh
Original file line number Diff line number Diff line change
@@ -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
Loading