diff --git a/base-kustomize/envoyproxy-gateway/base/kustomization.yaml b/base-kustomize/envoyproxy-gateway/base/kustomization.yaml index 96e49c570..aa708aa44 100644 --- a/base-kustomize/envoyproxy-gateway/base/kustomization.yaml +++ b/base-kustomize/envoyproxy-gateway/base/kustomization.yaml @@ -8,4 +8,3 @@ resources: - envoy-gateway.yaml - envoy-endpoint-policies.yaml - envoy-service-monitor.yaml - - all.yaml diff --git a/bin/setup-envoy-gateway.sh b/bin/setup-envoy-gateway.sh index ffdc93129..f710f990f 100755 --- a/bin/setup-envoy-gateway.sh +++ b/bin/setup-envoy-gateway.sh @@ -7,10 +7,11 @@ usage() { Usage: $0 [OPTIONS] OPTIONS: - -e, --email EMAIL Email address for ACME (required for ACME setup) - -d, --domain DOMAIN Gateway domain name (default: cluster.local) - -c, --challenge METHOD ACME challenge method: http01 or dns01 (default: http01) - -p, --dns-plugin PLUGIN DNS01 plugin (only used with dns01) + -c, --config FILE Configuration file for gateway setup (YAML format) + -e, --email EMAIL Email address for ACME (required for ACME setup, single gateway mode) + -d, --domain DOMAIN Gateway domain name (default: cluster.local, single gateway mode) + --challenge METHOD ACME challenge method: http01 or dns01 (default: http01, single gateway mode) + -p, --dns-plugin PLUGIN DNS01 plugin (only used with dns01, single gateway mode) -h, --help [PLUGIN] Display this help message, or detailed help for a specific plugin # Generic credentials (usage depends on --dns-plugin): @@ -46,16 +47,19 @@ For detailed help on a specific plugin, use: $0 --help PLUGIN Example: $0 --help cloudflare EXAMPLES: - # Basic setup with HTTP01 challenge + # Using configuration file (recommended for multiple gateways) + $0 --config /path/to/gateway-config.yaml + + # Basic setup with HTTP01 challenge (single gateway mode) $0 --email user@example.com --domain example.com - # Setup without ACME (no SSL certificates) + # Setup without ACME (no SSL certificates, single gateway mode) $0 --domain example.com # Get detailed help for Cloudflare $0 --help cloudflare - # Interactive mode + # Interactive mode (single gateway) $0 EOF } @@ -465,10 +469,12 @@ EOF } # Initialize variables +CONFIG_FILE="" ACME_EMAIL="" GATEWAY_DOMAIN="" CHALLENGE_METHOD="http01" DNS_PLUGIN="godaddy" +LEGACY_MODE=false # Generic credential variables API_KEY="" @@ -493,24 +499,33 @@ INTERACTIVE_MODE=true # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in + -c|--config) + CONFIG_FILE="$2" + INTERACTIVE_MODE=false + shift 2 + ;; -e|--email) ACME_EMAIL="$2" INTERACTIVE_MODE=false + LEGACY_MODE=true shift 2 ;; -d|--domain) GATEWAY_DOMAIN="$2" INTERACTIVE_MODE=false + LEGACY_MODE=true shift 2 ;; - -c|--challenge) + --challenge) CHALLENGE_METHOD="$2" INTERACTIVE_MODE=false + LEGACY_MODE=true shift 2 ;; -p|--dns-plugin) DNS_PLUGIN="$2" INTERACTIVE_MODE=false + LEGACY_MODE=true shift 2 ;; --api-key) @@ -646,6 +661,405 @@ if [ -z "${GATEWAY_DOMAIN}" ]; then GATEWAY_DOMAIN="cluster.local" fi +# Function to parse YAML config file +parse_config() { + local config_file="$1" + if [[ ! -f "$config_file" ]]; then + echo "Error: Configuration file '$config_file' not found" + exit 1 + fi + + # Validate YAML syntax + if ! python3 -c "import yaml; yaml.safe_load(open('$config_file'))" 2>/dev/null; then + echo "Error: Invalid YAML syntax in configuration file" + exit 1 + fi +} + +# Function to create namespace if it doesn't exist +create_namespace() { + local namespace="$1" + + if ! kubectl get namespace "$namespace" &>/dev/null; then + echo "Creating namespace: $namespace" + kubectl create namespace "$namespace" + + # Label the namespace for gateway usage + kubectl label namespace "$namespace" name="$namespace" --overwrite + kubectl label namespace "$namespace" app.kubernetes.io/name="envoy-gateway" --overwrite + else + echo "Namespace $namespace already exists" + fi +} + +# Function to create cluster issuer for multi-gateway +create_multi_cluster_issuer() { + local gateway_name="$1" + local issuer_type="$2" + local email="$3" + local challenge="$4" + local dns_plugin="$5" + local domain="$6" + + local issuer_name="${gateway_name}-issuer" + + if [[ "$issuer_type" == "selfsigned" ]]; then + echo "Creating self-signed cluster issuer for $gateway_name..." + cat < "/tmp/${route}-${gateway_name}-${gw_type}.yaml" + sed -i "s/namespace: nginx-gateway/namespace: ${namespace}/g" "/tmp/${route}-${gateway_name}-${gw_type}.yaml" + sed -i "s/name: flex-gateway/name: ${gw_instance_name}/g" "/tmp/${route}-${gateway_name}-${gw_type}.yaml" + + # Update parentRefs namespace to match the gateway namespace + sed -i "s/namespace: envoy-gateway/namespace: ${namespace}/g" "/tmp/${route}-${gateway_name}-${gw_type}.yaml" + sed -i "s/namespace: external-gateway/namespace: ${namespace}/g" "/tmp/${route}-${gateway_name}-${gw_type}.yaml" + + # Remove 'custom-' prefix from HTTPRoute metadata name and add gateway type suffix + sed -i "0,/^ name: /{s/^ name: custom-\(.*\)$/ name: \1-${gw_type}/;t;s/^ name: \(.*\)$/ name: \1-${gw_type}/}" "/tmp/${route}-${gateway_name}-${gw_type}.yaml" + + sudo mv "/tmp/${route}-${gateway_name}-${gw_type}.yaml" "$output_file" + echo " - Processed route: $route for $gw_type gateway" + done + else + echo " - Warning: Route file not found for $route" + fi + done + + # Apply the routes + if [[ -d "/etc/genestack/gateway-api/routes/${gateway_name}" ]]; then + kubectl apply -f "/etc/genestack/gateway-api/routes/${gateway_name}/" + fi +} + +# Function to process listeners for a specific gateway +process_gateway_listeners() { + local gateway_name="$1" + local namespace="$2" + local domain="$3" + local gateway_types="$4" + local routes_list="$5" + + echo "Processing listeners for gateway: $gateway_name" + + # Create gateway-specific listeners directory + sudo mkdir -p "/etc/genestack/gateway-api/listeners/${gateway_name}" + + # Process each listener in the list for each gateway type + for gw_type in $gateway_types; do + local gw_instance_name="${gateway_name}-${gw_type}" + + # Process all available listener files (not just those matching routes) + # This matches the legacy behavior where all listeners are applied + for listener_file in /opt/genestack/etc/gateway-api/listeners/*-https.json; do + if [[ -f "$listener_file" ]]; then + local listener_name=$(basename "$listener_file") + local output_file="/etc/genestack/gateway-api/listeners/${gateway_name}/${listener_name%.*}-${gw_type}.json" + local temp_file="/tmp/${listener_name%.*}-${gateway_name}-${gw_type}.json" + sed "s/your.domain.tld/${domain}/g" "$listener_file" > "$temp_file" + sudo mv "$temp_file" "$output_file" + echo " - Processed listener: $listener_name for $gw_type gateway" + fi + done + + # Apply the listeners for this gateway instance if any exist + if [[ -d "/etc/genestack/gateway-api/listeners/${gateway_name}" ]] && [[ -n "$(ls -A /etc/genestack/gateway-api/listeners/${gateway_name}/*-${gw_type}.json 2>/dev/null)" ]]; then + kubectl patch -n "$namespace" gateway "$gw_instance_name" \ + --type='json' \ + --patch="$(jq -s 'flatten | .' /etc/genestack/gateway-api/listeners/${gateway_name}/*-${gw_type}.json)" + echo " - Applied listeners to $gw_instance_name" + fi + done +} + # Function to validate and prompt for credentials based on DNS plugin validate_credentials() { local plugin="$1" @@ -825,25 +1239,151 @@ validate_credentials() { esac } -# Validate credentials if using DNS01 -if [[ "$CHALLENGE_METHOD" == "dns01" ]]; then - validate_credentials "$DNS_PLUGIN" -fi - -# Display configuration -echo "Configuration:" -echo " Email: ${ACME_EMAIL:-"(not provided - ACME setup will be skipped)"}" -echo " Domain: ${GATEWAY_DOMAIN}" -echo " Challenge Method: ${CHALLENGE_METHOD}" -if [[ "$CHALLENGE_METHOD" == "dns01" ]]; then - echo " DNS Plugin: ${DNS_PLUGIN}" -fi -echo +# Main execution logic +if [[ -n "$CONFIG_FILE" ]]; then + # Configuration file mode + echo "Using configuration file: $CONFIG_FILE" + parse_config "$CONFIG_FILE" + + # Apply only the base infrastructure (not the default flex-gateway) + echo "Applying base Envoy Gateway infrastructure..." + kubectl apply -f /opt/genestack/base-kustomize/envoyproxy-gateway/base/envoy-gateway-namespace.yaml + kubectl apply -f /opt/genestack/base-kustomize/envoyproxy-gateway/base/envoy-internal-gateway-issuer.yaml + kubectl apply -f /opt/genestack/base-kustomize/envoyproxy-gateway/base/envoy-custom-proxy-config.yaml + kubectl apply -f /opt/genestack/base-kustomize/envoyproxy-gateway/base/envoy-gatewayclass.yaml + kubectl apply -f /opt/genestack/base-kustomize/envoyproxy-gateway/base/envoy-endpoint-policies.yaml + kubectl apply -f /opt/genestack/base-kustomize/envoyproxy-gateway/base/envoy-service-monitor.yaml + echo "Skipping default flex-gateway creation (using config file gateways instead)" + + # Read the Python output and process each gateway + python3 > /tmp/gateway_configs.txt << EOF +import yaml +import sys + +with open('${CONFIG_FILE}', 'r') as f: + config = yaml.safe_load(f) + +if 'gateways' not in config: + print("Error: No 'gateways' section found in configuration file", file=sys.stderr) + sys.exit(1) + +for gateway in config['gateways']: + name = gateway.get('name', '') + namespace = gateway.get('namespace', name) # Default namespace to gateway name + domain = gateway.get('domain', 'cluster.local') + + # Handle both old format (string) and new format (list) for type + gateway_type = gateway.get('type', ['external']) + if isinstance(gateway_type, str): + gateway_type = [gateway_type] + gateway_types_str = ' '.join(gateway_type) + + # Handle both old format (single pool) and new format (pools dict) + metallb_pools = gateway.get('metallb_pools', gateway.get('metallb_pool', {})) + if isinstance(metallb_pools, str): + # Old format - single pool, assume it's external + external_pool = metallb_pools + internal_pool = '' + else: + # New format - dict with external/internal keys + external_pool = metallb_pools.get('external', '') + internal_pool = metallb_pools.get('internal', '') + + issuer = gateway.get('issuer', {}) + issuer_type = issuer.get('type', 'selfsigned') + email = issuer.get('email', '') + challenge = issuer.get('challenge', 'http01') + dns_plugin = issuer.get('dns_plugin', 'cloudflare') + + # Get DNS provider credentials from issuer config + api_token = issuer.get('api_token', '') + api_key = issuer.get('api_key', '') + + routes = gateway.get('routes', []) + routes_str = ' '.join(routes) + + if not name: + print("Error: Gateway name is required", file=sys.stderr) + sys.exit(1) + + print(f"{name}|{namespace}|{domain}|{gateway_types_str}|{external_pool}|{internal_pool}|{issuer_type}|{email}|{challenge}|{dns_plugin}|{api_token}|{api_key}|{routes_str}") +EOF + + # Process each gateway configuration + while IFS='|' read -r gw_name gw_namespace gw_domain gw_types external_pool internal_pool issuer_type issuer_email challenge dns_plugin api_token api_key routes; do + if [[ -n "$gw_name" ]]; then + echo "Processing gateway: $gw_name in namespace: $gw_namespace" + echo " Types: $gw_types" + echo " Domain: $gw_domain" + echo " External pool: ${external_pool:-"(none)"}" + echo " Internal pool: ${internal_pool:-"(none)"}" + + # Set credentials for this gateway + API_TOKEN="$api_token" + API_KEY="$api_key" + + # Create cluster issuer + create_multi_cluster_issuer "$gw_name" "$issuer_type" "$issuer_email" "$challenge" "$dns_plugin" "$gw_domain" + + # Create gateway instances + create_gateway "$gw_name" "$gw_namespace" "$gw_domain" "$gw_types" "$external_pool" "$internal_pool" + + # Wait for gateway instances to be programmed + for gw_type in $gw_types; do + gw_instance_name="${gw_name}-${gw_type}" + echo "Waiting for gateway $gw_instance_name to be programmed..." + kubectl -n "$gw_namespace" wait --timeout=5m "gateways.gateway.networking.k8s.io/$gw_instance_name" --for=condition=Programmed || { + echo "Warning: Gateway $gw_instance_name failed to become ready within timeout" + } + done + + # Process routes and listeners + if [[ -n "$routes" ]]; then + process_gateway_routes "$gw_name" "$gw_namespace" "$gw_domain" "$gw_types" "$routes" + process_gateway_listeners "$gw_name" "$gw_namespace" "$gw_domain" "$gw_types" "$routes" + fi + + echo "Gateway $gw_name setup complete" + echo + fi + done < /tmp/gateway_configs.txt + + rm -f /tmp/gateway_configs.txt + +elif [[ "$LEGACY_MODE" == "true" ]] || [[ "$INTERACTIVE_MODE" == "true" ]]; then + # Legacy single gateway mode or interactive mode + + # Validate credentials if using DNS01 + if [[ "$CHALLENGE_METHOD" == "dns01" ]]; then + validate_credentials "$DNS_PLUGIN" + fi -# Apply the gateway configuration -kubectl apply -k /etc/genestack/kustomize/envoyproxy-gateway/overlay -echo "Waiting for the gateway to be programmed" -kubectl -n envoy-gateway wait --timeout=5m gateways.gateway.networking.k8s.io flex-gateway --for=condition=Programmed + # Display configuration + echo "Legacy Mode Configuration:" + echo " Email: ${ACME_EMAIL:-"(not provided - ACME setup will be skipped)"}" + echo " Domain: ${GATEWAY_DOMAIN}" + echo " Challenge Method: ${CHALLENGE_METHOD}" + if [[ "$CHALLENGE_METHOD" == "dns01" ]]; then + echo " DNS Plugin: ${DNS_PLUGIN}" + fi + echo + + # Apply the gateway configuration + echo "Applying gateway configuration from kustomize..." + kubectl apply -k /etc/genestack/kustomize/envoyproxy-gateway/base + + # Give the gateway a moment to be created + sleep 2 + + echo "Waiting for the gateway to be created and programmed" + # Check if gateway exists + if kubectl -n envoy-gateway get gateway flex-gateway &>/dev/null; then + # Wait for it to be programmed + kubectl -n envoy-gateway wait --timeout=5m gateways.gateway.networking.k8s.io flex-gateway --for=condition=Programmed 2>/dev/null || true + else + echo "Warning: flex-gateway was not created by kustomize overlay. Checking what was created..." + kubectl -n envoy-gateway get gateways 2>/dev/null || echo "No gateways found in envoy-gateway namespace" + fi # Configure ACME if email is provided if [ ! -z "${ACME_EMAIL}" ]; then @@ -1375,22 +1915,39 @@ else echo "Skipping ACME configuration (no email provided)" fi -# Process routes +# Process routes (legacy behavior - all routes) sudo mkdir -p /etc/genestack/gateway-api/routes for route in $(ls -1 /opt/genestack/etc/gateway-api/routes); do sed "s/your.domain.tld/${GATEWAY_DOMAIN}/g" "/opt/genestack/etc/gateway-api/routes/${route}" > "/tmp/${route}" + # Update parentRefs namespace to envoy-gateway for legacy mode + sed -i "s/namespace: external-gateway/namespace: envoy-gateway/g" "/tmp/${route}" + sed -i "s/namespace: external-gateway/namespace: envoy-gateway/g" "/tmp/${route}" sudo mv -v "/tmp/${route}" "/etc/genestack/gateway-api/routes/${route}" done kubectl apply -f /etc/genestack/gateway-api/routes -# Process listeners +# Process listeners (legacy behavior - all listeners) sudo mkdir -p /etc/genestack/gateway-api/listeners for listener in $(ls -1 /opt/genestack/etc/gateway-api/listeners); do sed "s/your.domain.tld/${GATEWAY_DOMAIN}/g" "/opt/genestack/etc/gateway-api/listeners/${listener}" > "/tmp/${listener}" sudo mv -v "/tmp/${listener}" "/etc/genestack/gateway-api/listeners/${listener}" done -kubectl patch -n envoy-gateway gateway flex-gateway \ - --type='json' \ - --patch="$(jq -s 'flatten | .' /etc/genestack/gateway-api/listeners/*)" +# Only patch if gateway exists and there are listener files +if kubectl -n envoy-gateway get gateway flex-gateway &>/dev/null; then + if [ -n "$(ls -A /etc/genestack/gateway-api/listeners/*.json 2>/dev/null)" ]; then + echo "Patching flex-gateway with listeners..." + kubectl patch -n envoy-gateway gateway flex-gateway \ + --type='json' \ + --patch="$(jq -s 'flatten | .' /etc/genestack/gateway-api/listeners/*.json)" || true + fi +else + echo "Warning: flex-gateway does not exist, skipping listener patch" +fi + +else + echo "Error: No configuration provided. Use --config, provide legacy options, or run interactively." + echo "Use --help for usage information." + exit 1 +fi echo "Setup Complete" diff --git a/bin/setup-infrastructure.sh b/bin/setup-infrastructure.sh index 9abacd3ef..2340361cf 100755 --- a/bin/setup-infrastructure.sh +++ b/bin/setup-infrastructure.sh @@ -4,15 +4,28 @@ set -e set -o pipefail -if [ -z "${ACME_EMAIL}" ]; then - read -rp "Enter a valid email address for use with ACME, press enter to skip: " ACME_EMAIL - export ACME_EMAIL="${ACME_EMAIL:-}" -fi +# Gateway configuration - supports both legacy mode and config file mode +GATEWAY_CONFIG_FILE="${GATEWAY_CONFIG_FILE:-}" + +if [ -z "${GATEWAY_CONFIG_FILE}" ]; then + # Legacy mode - prompt for email and domain + if [ -z "${ACME_EMAIL}" ]; then + read -rp "Enter a valid email address for use with ACME, press enter to skip: " ACME_EMAIL + export ACME_EMAIL="${ACME_EMAIL:-}" + fi -if [ -z "${GATEWAY_DOMAIN}" ]; then - echo "The domain name for the gateway is required, if you do not have a domain name press enter to use the default" - read -rp "Enter the domain name for the gateway [cluster.local]: " GATEWAY_DOMAIN - export GATEWAY_DOMAIN="${GATEWAY_DOMAIN:-cluster.local}" + if [ -z "${GATEWAY_DOMAIN}" ]; then + echo "The domain name for the gateway is required, if you do not have a domain name press enter to use the default" + read -rp "Enter the domain name for the gateway [cluster.local]: " GATEWAY_DOMAIN + export GATEWAY_DOMAIN="${GATEWAY_DOMAIN:-cluster.local}" + fi +else + # Config file mode - check if file exists + if [ ! -f "${GATEWAY_CONFIG_FILE}" ]; then + echo "Error: Gateway configuration file not found: ${GATEWAY_CONFIG_FILE}" + exit 1 + fi + echo "Using gateway configuration file: ${GATEWAY_CONFIG_FILE}" fi if [ "${HYPERCONVERGED:-false}" = "true" ]; then @@ -138,7 +151,15 @@ kubectl apply -k /etc/genestack/kustomize/openstack/base /opt/genestack/bin/install-envoy-gateway.sh echo "Waiting for the envoyproxy-gateway to be available" kubectl -n envoyproxy-gateway-system wait --timeout=5m deployments.apps/envoy-gateway --for=condition=available -/opt/genestack/bin/setup-envoy-gateway.sh -e ${ACME_EMAIL} -d ${GATEWAY_DOMAIN} + +# Setup gateway - supports both legacy and config file modes +if [ -n "${GATEWAY_CONFIG_FILE}" ]; then + # Config file mode + /opt/genestack/bin/setup-envoy-gateway.sh --config "${GATEWAY_CONFIG_FILE}" +else + # Legacy mode + /opt/genestack/bin/setup-envoy-gateway.sh -e ${ACME_EMAIL} -d ${GATEWAY_DOMAIN} +fi # Run a rollout for cert-manager echo "Waiting for the cert-manager to be available" diff --git a/docs/gateway-setup.md b/docs/gateway-setup.md new file mode 100644 index 000000000..4a81dce3a --- /dev/null +++ b/docs/gateway-setup.md @@ -0,0 +1,458 @@ +# Enhanced Gateway Setup + +The `setup-envoy-gateway.sh` script has been enhanced to support multiple gateways with different configurations, allowing for more flexible deployments that can separate external and internal services with appropriate security configurations. + +## Features + +- **Multiple Gateways**: Configure multiple gateways with different domains and purposes +- **Namespace Isolation**: Each gateway runs in its own namespace for better security and organization +- **Hybrid Gateway Support**: Gateways can be external-only, internal-only, or both (hybrid) +- **Flexible Certificate Management**: Choose between Let's Encrypt or self-signed certificates per gateway +- **Route Segregation**: Assign specific OpenStack services to specific gateways +- **Security Zones**: Separate external-facing services from internal monitoring/admin tools +- **DNS Challenge Support**: Full support for DNS01 challenges with multiple providers +- **Multiple MetalLB Pools**: Support for different MetalLB address pools for external and internal access + +## Usage Modes + +### 1. Configuration File Mode (Recommended) + +Create a YAML configuration file and use it with the script: + +```bash +./bin/setup-envoy-gateway.sh --config /path/to/gateway-config.yaml +``` + +### 2. Legacy Single Gateway Mode + +For backward compatibility, the original single gateway setup is still supported: + +```bash +# With Let's Encrypt +./bin/setup-envoy-gateway.sh --email admin@example.com --domain cloud.example.com + +# With DNS01 challenge +./bin/setup-envoy-gateway.sh --email admin@example.com --domain cloud.example.com \ + --challenge dns01 --dns-plugin cloudflare --api-token YOUR_TOKEN +``` + +### 3. Interactive Mode + +Run without parameters for interactive prompts: + +```bash +./bin/setup-envoy-gateway.sh +``` + +## Configuration File Format + +```yaml +gateways: + - name: external-gateway # Required: Gateway name + namespace: external-gateway # Optional: Namespace (defaults to gateway name) + domain: cloud.example.com # Required: Domain for the gateway + type: # Required: List of gateway types + - external # Can be: external, internal, or both + metallb_pools: # Required: MetalLB address pools + external: gateway-api-external # Pool for external access + internal: gateway-api-internal # Pool for internal access (optional) + issuer: + type: letsencrypt # Required: letsencrypt or selfsigned + email: admin@example.com # Required for letsencrypt + challenge: http01 # Optional: http01 or dns01 (default: http01) + dns_plugin: cloudflare # Optional: DNS plugin for dns01 + api_token: "your-token" # DNS provider credentials (varies by provider) + routes: # Optional: List of services to route through this gateway + - keystone + - nova + - neutron +``` + +## Common Deployment Patterns + +### Pattern 1: External + Internal Separation + +```yaml +gateways: + # External gateway for user-facing OpenStack APIs + - name: external-gateway + namespace: external-gateway + domain: cloud.example.com + type: + - external + metallb_pools: + external: gateway-api-external + issuer: + type: letsencrypt + email: admin@example.com + challenge: http01 + routes: + - keystone + - nova + - neutron + - cinder + - glance + - heat + - octavia + - placement + + # Internal gateway for monitoring and admin tools + - name: internal-gateway + namespace: internal-gateway + domain: internal.cluster.local + type: + - internal + metallb_pools: + internal: gateway-api-internal + issuer: + type: selfsigned + routes: + - grafana + - prometheus + - alertmanager +``` + +### Pattern 2: Environment Separation + +```yaml +gateways: + # Production gateway with DNS01 challenge + - name: prod-gateway + namespace: prod-gateway + domain: cloud.example.com + type: + - external + metallb_pools: + external: gateway-api-prod + issuer: + type: letsencrypt + email: admin@example.com + challenge: dns01 + dns_plugin: cloudflare + api_token: "prod-token" + routes: + - keystone + - nova + - neutron + + # Development gateway with self-signed certs + - name: dev-gateway + namespace: dev-gateway + domain: dev.example.com + type: + - external + metallb_pools: + external: gateway-api-dev + issuer: + type: selfsigned + routes: + - keystone + - nova + - skyline +``` + +### Pattern 3: Service Segregation + +```yaml +gateways: + # Core compute services + - name: compute-gateway + namespace: compute-gateway + domain: compute.example.com + type: + - external + metallb_pools: + external: gateway-api-compute + issuer: + type: letsencrypt + email: admin@example.com + routes: + - keystone + - nova + - placement + - neutron + + # Storage services + - name: storage-gateway + namespace: storage-gateway + domain: storage.example.com + type: + - external + metallb_pools: + external: gateway-api-storage + issuer: + type: letsencrypt + email: admin@example.com + routes: + - cinder + - glance + + # Orchestration services + - name: orchestration-gateway + namespace: orchestration-gateway + domain: orchestration.example.com + type: + - external + metallb_pools: + external: gateway-api-orchestration + issuer: + type: letsencrypt + email: admin@example.com + routes: + - heat + - magnum +``` + +### Pattern 4: Hybrid Gateway (External + Internal) + +```yaml +gateways: + # Hybrid gateway accessible both externally and internally + - name: hybrid-gateway + namespace: hybrid-gateway + domain: cloud.example.com + type: + - external + - internal + metallb_pools: + external: gateway-api-external + internal: gateway-api-internal + issuer: + type: letsencrypt + email: admin@example.com + challenge: http01 + routes: + - keystone # Available on both external (port 443) and internal (port 443) + - nova # Available on both external (port 443) and internal (port 443) + - grafana # Available on both external (port 443) and internal (port 443) + + # Pure internal gateway for sensitive admin operations + - name: admin-gateway + namespace: admin-gateway + domain: admin.cluster.local + type: + - internal + metallb_pools: + internal: gateway-api-admin + issuer: + type: selfsigned + routes: + - prometheus + - alertmanager +``` + +## Available Routes + +The following OpenStack services can be routed through gateways: + +- `keystone` - Identity service +- `nova` - Compute service +- `neutron` - Networking service +- `cinder` - Block storage service +- `glance` - Image service +- `heat` - Orchestration service +- `octavia` - Load balancing service +- `placement` - Placement service +- `magnum` - Container orchestration service +- `barbican` - Key management service +- `blazar` - Resource reservation service +- `cloudkitty` - Billing service +- `freezer` - Backup service +- `ironic` - Bare metal service +- `masakari` - Instance high availability service +- `skyline` - Modern dashboard +- `grafana` - Monitoring dashboard +- `prometheus` - Metrics collection +- `alertmanager` - Alert management +- `loki` - Log aggregation + +## Architecture + +### Namespace Isolation +Each gateway is deployed in its own namespace, providing: +- **Security isolation**: Resources are separated by namespace boundaries +- **RBAC control**: Fine-grained access control per gateway +- **Resource management**: Independent resource quotas and limits +- **Operational clarity**: Clear separation of concerns + +### Gateway Types and Ports +- **External gateways**: Use standard ports 80/443 with external MetalLB pools +- **Internal gateways**: Use standard ports 80/443 with internal MetalLB pools +- **Hybrid gateways**: Create separate gateway instances for each type: + - `gateway-name-external`: Ports 80/443 with external pool + - `gateway-name-internal`: Ports 80/443 with internal pool + +### Route Distribution +Routes are automatically created for each gateway type: +- External routes reference `gateway-name-external` +- Internal routes reference `gateway-name-internal` +- Hybrid gateways get routes for both instances + +## MetalLB Configuration + +When using multiple gateways with different MetalLB pools, you need to ensure that each pool has a corresponding L2Advertisement resource. This allows MetalLB to advertise the IP addresses for each pool on the network. + +### Adding New MetalLB Pools + +If your configuration uses MetalLB pools other than the default `gateway-api-external` and `primary`, you need to add L2Advertisement resources for each pool. + +**Example:** If you add `gateway-api-internal` and `gateway-api-dev` pools to your configuration, add the following to `manifests/metallb/metallb-openstack-service-lb.yml`: + +```yaml +--- +apiVersion: metallb.io/v1beta1 +kind: IPAddressPool +metadata: + name: gateway-api-internal + namespace: metallb-system +spec: + addresses: + - 10.234.1.0/24 # Adjust to your internal network range + autoAssign: false + avoidBuggyIPs: true +--- +apiVersion: metallb.io/v1beta1 +kind: L2Advertisement +metadata: + name: internal-gateway-advertisement + namespace: metallb-system +spec: + ipAddressPools: + - gateway-api-internal + nodeSelectors: + - matchLabels: + node-role.kubernetes.io/worker: worker +--- +apiVersion: metallb.io/v1beta1 +kind: IPAddressPool +metadata: + name: gateway-api-dev + namespace: metallb-system +spec: + addresses: + - 10.234.2.0/24 # Adjust to your dev network range + autoAssign: false + avoidBuggyIPs: true +--- +apiVersion: metallb.io/v1beta1 +kind: L2Advertisement +metadata: + name: dev-gateway-advertisement + namespace: metallb-system +spec: + ipAddressPools: + - gateway-api-dev + nodeSelectors: + - matchLabels: + node-role.kubernetes.io/worker: worker +``` + +Then apply the updated configuration: + +```bash +kubectl apply -f manifests/metallb/metallb-openstack-service-lb.yml +``` + +### Important Notes + +- Each MetalLB pool must have a corresponding L2Advertisement +- The L2Advertisement tells MetalLB which nodes should advertise the pool's IP addresses +- Adjust the IP address ranges to match your network configuration +- The `autoAssign: false` setting prevents automatic assignment; gateways explicitly request pools via annotations + +## Security Considerations + +### External Gateways +- Use Let's Encrypt certificates for production +- Configure appropriate firewall rules +- Consider rate limiting and DDoS protection +- Use strong DNS provider credentials for DNS challenges +- Restrict external MetalLB pools to appropriate network segments + +### Internal Gateways +- Self-signed certificates are acceptable for internal use +- Ensure internal gateways are not accessible from external networks +- Use network policies to restrict access +- Consider using internal DNS for resolution +- Use dedicated internal MetalLB pools + +### Hybrid Gateways +- Carefully consider which services should be exposed both ways +- Use different MetalLB pools for external vs internal access +- Monitor access patterns to ensure appropriate usage +- Consider using different authentication mechanisms for internal vs external access + +## Troubleshooting + +### Certificate Issues +```bash +# Check certificate status +kubectl get certificates -A + +# Check cluster issuer status +kubectl get clusterissuers + +# Check certificate requests +kubectl get certificaterequests -A +``` + +### Gateway Status +```bash +# Check all gateways across namespaces +kubectl get gateways -A + +# Check gateways in a specific namespace +kubectl get gateways -n + +# Check gateway events +kubectl describe gateway -n + +# Check gateway instances for hybrid gateways +kubectl get gateways -n -l gateway.genestack.io/parent= +``` + +### Route Issues +```bash +# Check HTTP routes +kubectl get httproutes -A + +# Check route status +kubectl describe httproute -n +``` + +## Migration from Single Gateway + +To migrate from the existing single gateway setup: + +1. Create a configuration file with your current gateway settings +2. Add additional gateways as needed +3. Test the new configuration in a development environment +4. Apply the new configuration to production + +Example migration config: +```yaml +gateways: + # Existing gateway (maintains compatibility) + - name: flex-gateway + namespace: envoy-gateway # Keep in original namespace for compatibility + domain: your-current-domain.com + type: + - external + metallb_pools: + external: gateway-api-external # Your current pool + issuer: + type: letsencrypt # or selfsigned if you weren't using ACME + email: your-current-email@example.com + challenge: http01 # or dns01 if you were using that + routes: + # Add all the routes you currently have configured + - keystone + - nova + # ... etc +``` + +### Backward Compatibility Notes +- The legacy single gateway mode still works unchanged +- Existing `flex-gateway` in `envoy-gateway` namespace is preserved +- New gateways use their own namespaces by default +- Configuration file format supports both old and new syntax \ No newline at end of file diff --git a/etc/gateway-api/listeners/alertmanager-https.json b/etc/gateway-api/listeners/alertmanager-https.json new file mode 100644 index 000000000..2ad812177 --- /dev/null +++ b/etc/gateway-api/listeners/alertmanager-https.json @@ -0,0 +1,27 @@ +[ + { + "op": "add", + "path": "/spec/listeners/-", + "value": { + "name": "am-https", + "port": 443, + "protocol": "HTTPS", + "hostname": "alertmanager.your.domain.tld", + "allowedRoutes": { + "namespaces": { + "from": "All" + } + }, + "tls": { + "certificateRefs": [ + { + "group": "", + "kind": "Secret", + "name": "alertmanager-gw-tls-secret" + } + ], + "mode": "Terminate" + } + } + } +] diff --git a/etc/gateway-api/listeners/loki-https.json b/etc/gateway-api/listeners/loki-https.json index ec19000c8..a623e0099 100644 --- a/etc/gateway-api/listeners/loki-https.json +++ b/etc/gateway-api/listeners/loki-https.json @@ -6,7 +6,7 @@ "name": "loki-http", "port": 80, "protocol": "HTTP", - "hostname": "internal.loki-gateway.your.domain.tld", + "hostname": "loki-gateway.your.domain.tld", "allowedRoutes": { "namespaces": { "from": "All" diff --git a/etc/gateway-api/listeners/longhorn-https.json b/etc/gateway-api/listeners/longhorn-https.json new file mode 100644 index 000000000..e28da4ecc --- /dev/null +++ b/etc/gateway-api/listeners/longhorn-https.json @@ -0,0 +1,27 @@ +[ + { + "op": "add", + "path": "/spec/listeners/-", + "value": { + "name": "longhorn-https", + "port": 443, + "protocol": "HTTPS", + "hostname": "longhorn.your.domain.tld", + "allowedRoutes": { + "namespaces": { + "from": "All" + } + }, + "tls": { + "certificateRefs": [ + { + "group": "", + "kind": "Secret", + "name": "longhorn-gw-tls-secret" + } + ], + "mode": "Terminate" + } + } + } +] diff --git a/etc/gateway-api/listeners/prometheus-https.json b/etc/gateway-api/listeners/prometheus-https.json new file mode 100644 index 000000000..47d84e66a --- /dev/null +++ b/etc/gateway-api/listeners/prometheus-https.json @@ -0,0 +1,27 @@ +[ + { + "op": "add", + "path": "/spec/listeners/-", + "value": { + "name": "prometheus-https", + "port": 443, + "protocol": "HTTPS", + "hostname": "prometheus.your.domain.tld", + "allowedRoutes": { + "namespaces": { + "from": "All" + } + }, + "tls": { + "certificateRefs": [ + { + "group": "", + "kind": "Secret", + "name": "prometheus-gw-tls-secret" + } + ], + "mode": "Terminate" + } + } + } +] diff --git a/etc/gateway-api/listeners/rabbitmq-https.json b/etc/gateway-api/listeners/rabbitmq-https.json new file mode 100644 index 000000000..9bfe27a3f --- /dev/null +++ b/etc/gateway-api/listeners/rabbitmq-https.json @@ -0,0 +1,27 @@ +[ + { + "op": "add", + "path": "/spec/listeners/-", + "value": { + "name": "rabbitmq-https", + "port": 443, + "protocol": "HTTPS", + "hostname": "rabbitmq.your.domain.tld", + "allowedRoutes": { + "namespaces": { + "from": "All" + } + }, + "tls": { + "certificateRefs": [ + { + "group": "", + "kind": "Secret", + "name": "rabbitmq-gw-tls-secret" + } + ], + "mode": "Terminate" + } + } + } +] diff --git a/etc/gateway-api/routes/custom-alertmanager-routes.yaml b/etc/gateway-api/routes/custom-alertmanager-routes.yaml index 45d74b7ad..9b48ab74d 100644 --- a/etc/gateway-api/routes/custom-alertmanager-routes.yaml +++ b/etc/gateway-api/routes/custom-alertmanager-routes.yaml @@ -2,12 +2,12 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: - name: custom-alertmanager-gateway-route + name: alertmanager-gateway-route namespace: prometheus spec: parentRefs: - name: flex-gateway - sectionName: http + sectionName: am-https namespace: envoy-gateway hostnames: - "alertmanager.your.domain.tld" diff --git a/etc/gateway-api/routes/custom-grafana-routes.yaml b/etc/gateway-api/routes/custom-grafana-routes.yaml index 057258661..c40d7646a 100644 --- a/etc/gateway-api/routes/custom-grafana-routes.yaml +++ b/etc/gateway-api/routes/custom-grafana-routes.yaml @@ -7,10 +7,6 @@ metadata: spec: hostnames: - grafana.your.domain.tld - - grafana.cluster.local - - grafana - - grafana.grafana - - grafana.grafana.svc.cluster.local parentRefs: - group: gateway.networking.k8s.io kind: Gateway diff --git a/etc/gateway-api/routes/custom-loki-internal-routes.yaml b/etc/gateway-api/routes/custom-loki-internal-routes.yaml index 86a3e2b26..e226a5a51 100644 --- a/etc/gateway-api/routes/custom-loki-internal-routes.yaml +++ b/etc/gateway-api/routes/custom-loki-internal-routes.yaml @@ -6,7 +6,7 @@ metadata: namespace: grafana spec: hostnames: - - internal.loki-gateway.your.domain.tld + - loki-gateway.your.domain.tld parentRefs: - name: flex-gateway namespace: envoy-gateway diff --git a/etc/gateway-api/routes/custom-longhorn-routes.yaml b/etc/gateway-api/routes/custom-longhorn-routes.yaml new file mode 100644 index 000000000..33667e0b8 --- /dev/null +++ b/etc/gateway-api/routes/custom-longhorn-routes.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: longhorn-gateway-route + namespace: longhorn-system +spec: + hostnames: + - longhorn.your.domain.tld + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: flex-gateway + namespace: envoy-gateway + sectionName: longhorn-https + rules: + - backendRefs: + - group: "" + kind: Service + name: longhorn-frontend + port: 80 + weight: 1 + matches: + - path: + type: PathPrefix + value: / diff --git a/etc/gateway-api/routes/custom-prometheus-gateway-route.yaml b/etc/gateway-api/routes/custom-prometheus-gateway-route.yaml index 3f2be161e..21d63eb67 100644 --- a/etc/gateway-api/routes/custom-prometheus-gateway-route.yaml +++ b/etc/gateway-api/routes/custom-prometheus-gateway-route.yaml @@ -2,7 +2,7 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: - name: custom-prometheus-gateway-route + name: prometheus-gateway-route namespace: prometheus spec: hostnames: @@ -10,7 +10,7 @@ spec: parentRefs: - name: flex-gateway namespace: envoy-gateway - sectionName: http + sectionName: prometheus-https rules: - backendRefs: - name: kube-prometheus-stack-prometheus diff --git a/etc/gateway-api/routes/custom-rabbitmq-routes.yaml b/etc/gateway-api/routes/custom-rabbitmq-routes.yaml new file mode 100644 index 000000000..e02921f63 --- /dev/null +++ b/etc/gateway-api/routes/custom-rabbitmq-routes.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: rabbitmq-gateway-route + namespace: openstack +spec: + hostnames: + - rabbitmq.your.domain.tld + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: flex-gateway + namespace: envoy-gateway + sectionName: rabbitmq-https + rules: + - backendRefs: + - group: "" + kind: Service + name: rabbitmq + port: 15672 + weight: 1 + matches: + - path: + type: PathPrefix + value: / diff --git a/examples/gateway-config.yaml b/examples/gateway-config.yaml new file mode 100644 index 000000000..a113cd68d --- /dev/null +++ b/examples/gateway-config.yaml @@ -0,0 +1,102 @@ +# Example Gateway Configuration for Genestack +# This file demonstrates how to configure multiple gateways with different +# certificate issuers, route configurations, and namespace isolation + +gateways: + # External-only gateway with Let's Encrypt certificates + - name: external-gateway + namespace: external-gateway # Each gateway gets its own namespace + domain: cloud.example.com + type: + - external + metallb_pools: + external: gateway-api-external + issuer: + type: letsencrypt + email: admin@example.com + challenge: http01 # or dns01 + # For DNS01 challenge with Cloudflare (example): + # challenge: dns01 + # dns_plugin: cloudflare + # api_token: "your-cloudflare-api-token" + routes: + - keystone + - nova + - neutron + - cinder + - glance + - horizon + - heat + - octavia + - placement + + # Internal-only gateway with self-signed certificates for monitoring/admin tools + - name: internal-gateway + namespace: internal-gateway + domain: internal.cluster.local + type: + - internal + metallb_pools: + internal: gateway-api-internal + issuer: + type: selfsigned + routes: + - grafana + - prometheus + - alertmanager + - loki + + # Hybrid gateway - both external and internal access + - name: hybrid-gateway + namespace: hybrid-gateway + domain: hybrid.example.com + type: + - external + - internal + metallb_pools: + external: gateway-api-external + internal: gateway-api-internal + issuer: + type: letsencrypt + email: admin@example.com + challenge: http01 + routes: + - keystone # Available on both external and internal + - grafana # Available on both external and internal + + # Development gateway with self-signed certificates + - name: dev-gateway + namespace: dev-gateway + domain: dev.example.com + type: + - external + metallb_pools: + external: gateway-api-dev + issuer: + type: selfsigned + routes: + - keystone + - nova + - neutron + - skyline + + # Staging gateway with Let's Encrypt DNS01 challenge + - name: staging-gateway + namespace: staging-gateway + domain: staging.example.com + type: + - external + metallb_pools: + external: gateway-api-staging + issuer: + type: letsencrypt + email: staging@example.com + challenge: dns01 + dns_plugin: cloudflare + api_token: "your-staging-cloudflare-token" + routes: + - keystone + - nova + - neutron + - cinder + - glance \ No newline at end of file diff --git a/examples/simple-gateway-config.yaml b/examples/simple-gateway-config.yaml new file mode 100644 index 000000000..0d50781a4 --- /dev/null +++ b/examples/simple-gateway-config.yaml @@ -0,0 +1,57 @@ +# Simple Gateway Configuration +# Basic setup with external gateway using Let's Encrypt and internal gateway for monitoring + +gateways: + # Main external gateway for OpenStack services + - name: external-gateway + namespace: external-gateway + domain: cloud.example.com + type: + - external + metallb_pools: + external: gateway-api-external + issuer: + type: letsencrypt + email: admin@example.com + challenge: http01 + routes: + - keystone + - nova + - neutron + - cinder + - glance + - heat + - octavia + - placement + + # Internal gateway for monitoring and admin tools + - name: internal-gateway + namespace: internal-gateway + domain: internal.cluster.local + type: + - internal + metallb_pools: + internal: gateway-api-internal + issuer: + type: selfsigned + routes: + - grafana + - prometheus + + # Hybrid gateway example - accessible both externally and internally + - name: admin-gateway + namespace: admin-gateway + domain: admin.example.com + type: + - external + - internal + metallb_pools: + external: gateway-api-external + internal: gateway-api-internal + issuer: + type: letsencrypt + email: admin@example.com + challenge: http01 + routes: + - keystone + - grafana \ No newline at end of file