Secure communication
Lightning AI deployments support end-to-end TLS encryption, a critical feature for achieving a SOC 2 compliant production environment.
When you enable this feature, the Lightning Platform passes the necessary TLS certificate and private key to your container as the LIGHTNING_CERT_PEM and LIGHTNING_KEY_FILE environment variables.
Your application is responsible for loading these certificates to activate secure (HTTPS) communication.
Python FastAPI example
Below is a reference implementation for a Python FastAPI server.
# Standard library imports for handling environment variables, files, and data encoding
import base64
import os
import tempfile
from pathlib import Path
from typing import Any, Dict
# Third-party imports for the web server
import uvicorn
from fastapi import FastAPI
def load_ssl_context() -> Dict[str, Any]:
"""
Checks for TLS certificate environment variables and prepares them for Uvicorn.
If `LIGHTNING_CERT_PEM` and `LIGHTNING_KEY_FILE` are found, this function
decodes them from base64, writes them to temporary files, and returns a
dictionary with paths for Uvicorn to use. Otherwise, it returns an empty dict.
"""
cert_pem_b64 = os.getenv("LIGHTNING_CERT_PEM")
key_pem_b64 = os.getenv("LIGHTNING_KEY_FILE")
# Proceed only if both environment variables are set.
if cert_pem_b64 and key_pem_b64:
try:
# Decode the base64 strings to get the actual PEM content.
cert_pem = base64.b64decode(cert_pem_b64).decode('utf-8')
key_pem = base64.b64decode(key_pem_b64).decode('utf-8')
# Create temporary files to hold the certificate and key.
# IMPORTANT: `delete=False` means these files will NOT be automatically
# cleaned up when the server stops. This is required by this specific
# logic because Uvicorn needs to read the files after this function
# returns. In a production system, you should consider a cleanup
# strategy for these files upon server shutdown.
with tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix=".pem") as cert_file, \
tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix=".pem") as key_file:
cert_file.write(cert_pem)
key_file.write(key_pem)
# Return a dictionary with Path objects for Uvicorn's SSL arguments.
print("TLS certificates found. Starting server with HTTPS.")
return {
"ssl_keyfile": Path(key_file.name),
"ssl_certfile": Path(cert_file.name)
}
except (base64.binascii.Error, IOError) as e:
# If there's an error decoding or writing, fall back to HTTP.
print(f"WARNING: Error processing certificates: {e}. Starting without HTTPS.")
# If variables are not set or an error occurred, return an empty dictionary.
print("TLS certificates not found. Starting server with HTTP.")
return {}
app = FastAPI(
title="Secure FastAPI Server",
description="An example server that can be run with or without TLS."
)
@app.get("/")
def read_root():
"""A simple endpoint to confirm the server is running."""
return {"message": "Hello from your FastAPI server!"}
if __name__ == "__main__":
# The `load_ssl_context()` function returns a dictionary.
# The `**` operator unpacks this dictionary into keyword arguments for `uvicorn.run`.
# - If certs are found, it's like calling:
# run(app, ssl_keyfile=Path(...), ssl_certfile=Path(...))
# - If not found, it's like calling:
# run(app)
ssl_config = load_ssl_context()
uvicorn.run(
app,
host="0.0.0.0",
port=8000,
**ssl_config
)
Note: If you are using LitServe, this is done automatically for you.
Go example
This code is a concise, runnable example that demonstrates the core task. It's heavily commented to explain each step, especially the handling of base64-encoded certificate data.
package main
# [Imports]
import (
"crypto/tls"
"encoding/base64"
"fmt"
"log"
"net/http"
"time"
)
# [Prepare SSL Context]
# createTLSConfig prepares the TLS configuration from base64-encoded PEM data.
# In a real application, these strings would come from environment variables.
func createTLSConfig() *tls.Config {
# Assume these base64-encoded variables are passed to your application.
# For this example, they are hardcoded. In production, you would use:
certPEM_b64 := os.Getenv("LIGHTNING_CERT_PEM")
keyPEM_b64 := os.Getenv("LIGHTNING_KEY_FILE")
# The logic will only proceed if both variables are non-empty.
if certPEM_b64 == "" || keyPEM_b64 == "" {
return nil # No certificates provided, so no TLS config.
}
# Decode the base64 strings to get the raw PEM data.
certPEM, err := base64.StdEncoding.DecodeString(certPEM_b64)
if err != nil {
log.Printf("WARNING: Failed to decode certificate, starting without TLS: %v", err)
return nil
}
keyPEM, err := base64.StdEncoding.DecodeString(keyPEM_b64)
if err != nil {
log.Printf("WARNING: Failed to decode private key, starting without TLS: %v", err)
return nil
}
# Create a TLS certificate object directly from the PEM data.
cert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
log.Printf("WARNING: Failed to load TLS key pair, starting without TLS: %v", err)
return nil
}
# Return a TLS configuration containing the certificate.
return &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
}
}
# [Create and run Server]
func main() {
# Define a simple handler for the root path.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello from your Go server!")
})
# Prepare the server configuration.
server := &http.Server{
Addr: ":8080",
ReadHeaderTimeout: 3 * time.Second,
# Get the TLS configuration by calling our function.
TLSConfig: createTLSConfig(),
}
# Conditionally start the server with or without TLS.
if server.TLSConfig != nil {
# If TLSConfig is not nil, start an HTTPS server.
# The certFile and keyFile arguments are empty because the certificates
# are already loaded in the server's TLSConfig.
log.Println("TLS configuration found. Starting server with HTTPS on port 8080.")
if err := server.ListenAndServeTLS("", ""); err != nil {
log.Fatalf("FATAL: HTTPS server failed: %v", err)
}
} else {
# If TLSConfig is nil, start a standard HTTP server.
log.Println("TLS configuration not found. Starting server with HTTP on port 8080.")
if err := server.ListenAndServe(); err != nil {
log.Fatalf("FATAL: HTTP server failed: %v", err)
}
}
}Was this page helpful?
Thanks for the feedback.