Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support HTTPS with Self-Signed Certificates #52

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
85 changes: 84 additions & 1 deletion .github/workflows/pipeline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ jobs:
with :
python-version: ${{ env.PYTHON_VERSION }}

- name: Generate TLS certificate
run: make generate-tls-cert

- name: Install Docker
# moby-runc is a CLI tool for spawning and running containers according to the Open Container Initiative (OCI)
# specification, and it is used by the Docker runtime. The existing version of moby-runc on the GitHub Actions runner
Expand Down Expand Up @@ -135,6 +138,9 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Generate TLS certificate
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once I figure out how to optionally check for TLS certificate in NGINX config, I will remove this step since motion detection tests actually run just with HTTP.

run: make generate-tls-cert

- name: Install Docker
run: |
sudo apt-get update
Expand Down Expand Up @@ -211,4 +217,81 @@ jobs:
# This ensures that we always stop the container regardless of the outcomes of
# the previous steps
if: always()
run: docker stop ${{ steps.start_container.outputs.container_id }}
run: docker stop ${{ steps.start_container.outputs.container_id }}

test-sdk-with-https:
runs-on: ubuntu-22.04
steps:
- name: Check out code
uses: actions/checkout@v3

- name: Set up python
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Generate TLS Certificate
run: make generate-tls-cert

- name: Install Docker
run: |
sudo apt-get update
sudo apt-get remove moby-runc
sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt-get update
sudo apt-get install docker-ce

- name: Build Docker Image
run: docker build --tag edge-endpoint .

- name: Start Docker Container
id: start_container
run: |
source test/setup_plain_test_env.sh
echo "EDGE_CONFIG=$EDGE_CONFIG"
container_id=$(docker run \
-e LOG_LEVEL=DEBUG \
-e EDGE_CONFIG \
-d -p 6717:443 \
edge-endpoint)
echo "::set-output name=container_id::$container_id"

- name: Install poetry
uses: snok/install-poetry@v1
with:
version: ${{ env.POETRY_VERSION }}
virtualenvs-create: true
virtualenvs-in-project: true
installer-parallel: true

# Note that we're pulling the latest main from the SDK repo
# This might be ahead of what's published to pypi, but it's useful to test things before they're released.
- name: Checkout Groundlight SDK
uses: actions/checkout@v3
with:
repository: groundlight/python-sdk
path: groundlight-sdk
ref: disable-tls-verification

- name: Install Groundlight SDK dependencies
run: |
cd groundlight-sdk
cat src/groundlight/client.py
poetry install

- name: Run SDK tests
run: |
export DISABLE_TLS_VERIFY=1 # disable TLS verification
GROUNDLIGHT_ENDPOINT=https://localhost:6717
cd groundlight-sdk
poetry run pytest

- name: Dump Logs from Docker Container
if: always()
run: docker logs ${{ steps.start_container.outputs.container_id }}

- name: Stop Docker Container
if: always()
run: docker stop ${{ steps.start_container.outputs.container_id }}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ __pycache__/
# C extensions
*.so

# SSL-related
certs/ssl/nginx_ed25519.key
certs/ssl/nginx_ed25519.crt

# Distribution / packaging
.Python
build/
Expand Down
8 changes: 8 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ RUN apt-get update \
nginx \
libglib2.0-0 \
libgl1-mesa-glx \
coreutils \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& curl -sSL https://install.python-poetry.org | python -
Expand Down Expand Up @@ -59,6 +60,9 @@ COPY configs ${APP_ROOT}/configs
COPY deploy/k3s/inference_deployment/inference_deployment_template.yaml \
/etc/groundlight/inference-deployment/

RUN mkdir -p /etc/nginx/ssl

COPY certs/ssl /etc/nginx/ssl

##################
# Production Stage
Expand All @@ -77,6 +81,10 @@ WORKDIR ${APP_ROOT}
COPY /app ${APP_ROOT}/app/

COPY --from=production-dependencies-build-stage ${APP_ROOT}/configs/nginx.conf /etc/nginx/nginx.conf
COPY --from=production-dependencies-build-stage /etc/nginx/ssl /etc/nginx/ssl

# Update certificates
RUN update-ca-certificates

# Remove default nginx config
RUN rm /etc/nginx/sites-enabled/default
Expand Down
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,9 @@ lint: install-lint ## Run linter to check formatting and style

format: install-lint ## Run standard python formatting
./code-quality/format ${LINT_PATHS}

# OpenSSL related commands
generate-tls-cert:
mkdir -p certs/ssl
./certs/generate-tls-cert.sh
sudo chmod 644 certs/ssl/*
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,24 @@ docker run -d --name groundlight-edge -e GROUNDLIGHT_API_TOKEN --rm -p 127.0.0.1

### Configuring HTTPS on the NGINX proxy

Because the first server application code reaches is always the NGINX proxy, standard nginx configuration can be used
to configure HTTPS. You must either supply a signed TLS certificate or generate a self-signed certificate in this case.
When using a self-signed certificate, be sure to configure calling applications to ignore TLS warnings.
Because the first server application code reaches is always the NGINX proxy, standard NGINX configuration can be used
to configure HTTPS. The NGINX configuration file [`nginx.conf`](./configs/nginx.conf) is already set up for both
HTTP and HTTPS.

To set up TLS, modify the [`nginx.conf`](./configs/nginx.conf) file. Then rebuild your container and relaunch the server.
In order to use HTTPS for the edge endpoint, you must either supply a signed TLS certificate or generate a self-signed
certificate. You can generate a self-signed certificate with OpenSSL by running

```shell
make generate-tls-cert
```

This will create both a private key and a TLS certificate stored at `certs/ssl/nginx_ed25519.key` and `certs/ssl/nginx_ed25519.crt`.
Then, rebuild and re-lauch the server, making sure to specify the port for HTTPS, which is 443, instead of 6717 for HTTP.

```shell
docker build --tag edge-endpoint
export EDGE_CONFIG=$(cat configs/edge-config.yaml)
# Send requests to the HTTPS server
docker run -d --name groundlight-edge -e EDGE_CONFIG --rm -p 6717:443 edge-endpoint
```

1 change: 1 addition & 0 deletions app/core/app_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def load_edge_config() -> RootEdgeConfig:

@lru_cache(maxsize=MAX_SDK_INSTANCES_CACHE_SIZE)
def _get_groundlight_sdk_instance_internal(api_token: str):
logger.debug(f"Creating new Groundlight SDK instance with API token: {api_token}")
return Groundlight(api_token=api_token)


Expand Down
35 changes: 35 additions & 0 deletions certs/generate-tls-cert.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/bash

set -ex

# Function to check if openssl is installed. If not exist
# then install it.
check_openssl() {
if ! command -v openssl &> /dev/null
then
echo "openssl could not be found"
echo "Installing openssl..."
sudo apt-get install openssl
fi
}

# Check if openssl is installed
check_openssl

# Change to current directory
cd $(dirname $0)

# Set TLS_CERT_DIR to current directory
TLS_CERT_DIR=$(pwd)/ssl

# Generate an Ed25519 Private key
sudo openssl genpkey -algorithm Ed25519 -out ${TLS_CERT_DIR}/nginx_ed25519.key

# Generate a self-signed certificate using the Ed25519 Private key
# Valid for 365 days
sudo openssl req -new -x509 \
-config ssl/openssl-custom.cnf \
-batch \
-key ${TLS_CERT_DIR}/nginx_ed25519.key \
-out ${TLS_CERT_DIR}/nginx_ed25519.crt \
-days 365
13 changes: 13 additions & 0 deletions certs/ssl/openssl-custom.cnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# https://www.phildev.net/ssl/opensslconf.html

[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
prompt = no

[req_distinguished_name]
countryName = US

[v3_ca]
basicConstraints = CA:TRUE
keyUsage = digitalSignature, keyCertSign, cRLSign
57 changes: 56 additions & 1 deletion configs/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ events {
}

http {

server {
listen 6717;

Expand All @@ -23,4 +24,58 @@ http {
}

}
}

#https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/
server {
listen 443 ssl; # HTTPS server
server_name edge-endpoint-service;

# If SSL certificate does not exist, then fallback to HTTP
if (!-f /etc/nginx/ssl/nginx_ed25519.crt) {
rewrite ^ http://$host$request_uri? permanent;
}

# Point to the self-signed certificate and key
ssl_certificate /etc/nginx/ssl/nginx_ed25519.crt;
ssl_certificate_key /etc/nginx/ssl/nginx_ed25519.key;

# SSL settings.

# ssl_protocols: Only newer versions of TLS are allowed.
# ssl_ciphers: Specifies a list of ciphers that the server is willing to use.
# The given list includes modern, secure ciphers.
# Refer to https://mozilla.github.io/server-side-tls/ssl-config-generator/ for more details.

# ssl_prefer_server_ciphers: Dictates who gets to choose the cipher for the SSL/TLS connection.
# When set to on, the server's list of ciphers takes priority.
# Here it's set to off, allowing client preference but still bound by the server's list.
# ssl_session_cache: Enables session caching for SSL/TLS, which can improve performance.
# By caching the session, repeat handshakes within a short period can be avoided.
# In this configuration, the cache is shared among all workers and has a 10-minute lifespan.


proxy_ssl_server_name on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
# ssl_ciphers HIGH:!aNULL:!MD5;
ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;

location / {
proxy_pass http://localhost:6718;

# If local edge server is not up or can't handle the query arguments,
# then fallback to the cloud API server.
# 422 - Unprocessable Entity
# 404 - Not Found
proxy_intercept_errors on;
error_page 404 422 405 = @fallback;
}

location @fallback {
# Fallback to the cloud API server
proxy_pass https://api.groundlight.ai;
}
}

}
11 changes: 10 additions & 1 deletion deploy/bin/cluster_setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ fail() {

K="k3s kubectl"
INFERENCE_FLAVOR=${INFERENCE_FLAVOR:-"GPU"}
USE_HTTPS=${USE_HTTPS:-"0"}

# Secrets
./deploy/bin/make-gl-api-token-secret.sh
Expand Down Expand Up @@ -65,6 +66,14 @@ $K get service -o custom-columns=":metadata.name" --no-headers=true | \
xargs -I {} $K delete service {}

# Reapply changes
$K apply -f deploy/k3s/edge_deployment/edge_deployment.yaml
if [[ "${USE_HTTPS}" == "1" ]]; then
echo "Using HTTPS. Expecting a TLS certificate and a private key to have been generated."
./deploy/bin/make-tls-cert-secret.sh
$K kustomize deploy/k3s/edge_deployment > edge_deployment.yaml
$K apply -f edge_deployment.yaml
rm edge_deployment.yaml
else
$K apply -f deploy/k3s/edge_deployment/edge_deployment.yaml
fi

$K describe deployment edge-endpoint
21 changes: 21 additions & 0 deletions deploy/bin/make-tls-cert-secret.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/bash

K="k3s kubectl"
TLS_PRIVATE_KEY="certs/ssl/nginx_ed25519.key"
TLS_CERTIFICATE="certs/ssl/nginx_ed25519.crt"

$K delete --ignore-not-found secret tls-certificate


# First check if the certs/ssl/nginx_ed25519.key and certs/ssl/nginx_ed25519.crt exist
# If not exit early. Using exit 0 instead of exit 1 since this is an optional secret.
if [ ! -f "$TLS_PRIVATE_KEY" ] || [ ! -f "$TLS_CERTIFICATE" ]; then
echo "TLS certificate and key not found at the desired location. Exiting..."
exit 0
fi


# Create a kubernetes secret for the TLS certificate and private key
$K create secret generic tls-certificate \
--from-file=nginx_ed25519.key=${TLS_PRIVATE_KEY} \
--from-file=nginx_ed25519.crt=${TLS_CERTIFICATE}
29 changes: 29 additions & 0 deletions deploy/k3s/edge_deployment/conditional_volume_patch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
apiVersion: apps/v1
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of this is also not necessary! Will get rid of it and remove any scripting that was setup to apply this deployment patch.

kind: Deployment
metadata:
name: edge-endpoint
labels:
app: edge-endpoint
spec:
replicas: 1
selector:
matchLabels:
app: edge-logic-server
template:
metadata:
labels:
app: edge-logic-server
spec:
serviceAccountName: edge-endpoint-service-account
containers:
- name: edge-endpoint
volumeMounts:
- name: tsl-certificate-volume
mountPath: /etc/nginx/ssl
readOnly: true
volumes:
# Expecting the `tls-certificate` secret to have been
# generated before applying this patch
- name: tls-certificate-volume
secret:
secretName: tls-certificate
Loading