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

chart: refactoring config keys to enable secure connection #2306

Merged
merged 1 commit into from
Jul 16, 2024

Conversation

VietND96
Copy link
Member

@VietND96 VietND96 commented Jul 16, 2024

User description

Thanks for contributing to the Docker-Selenium project!
A PR well described will help maintainers to quickly review and merge it

Before submitting your PR, please check our contributing guidelines, applied for this repository.
Avoid large PRs, help reviewers by making them as simple and short as possible.

Description

Motivation and Context

Refactoring config keys in the chart to enable secure connection
Fix #2293

Configuration of Secure Communication

Selenium Grid supports secure communication between components. Refer to the instructions and options are able to configure the secure communication. Below is the details on how to enable secure communication in Selenium Grid chart.

In the chart, there is directory certs contains the default self-signed certificate, private key (as PKCS8 format), and Java Keystore (JKS) to teach Java about secure connection (since we are using a non-standard CA) for your trial, local testing purpose. You can generate your own self-signed certificate put them in that default directory by using script certs/cert.sh with adjust needed information. The certificate, private key, truststore are mounted to the components via Secret.

Usage of certs/cert.sh script:

# Generate self-signed to target directory
./certs/cert.sh -d /path/to/your/
# Add current host IP to the certificate
ADD_IP_ADDRESS=hostname ./certs/cert.sh -d /path/to/your/
# Add multiple IP addresses to the certificate (comma-separated)
ADD_IP_ADDRESS=",IP:10.10.10.10,IP:10.10.11.11" ./certs/cert.sh -d /path/to/your/
# Other environment variables that script consumes
# CERTNAME, STOREPASS, KEYPASS, ALIAS, SERVER_KEYSTORE, BASE64_ONLY

Create TLS Secret

There are multiple ways to insert your certificate, private key, truststore to the components. You can choose one of following ways:

  1. Replace your certificate, private key, truststore to the default directory certs in chart with the same name before deploying the chart.

  2. Use Helm CLI to pass your certificate, private key, truststore via --set-file when deploying the chart. For example (replace $RELEASENAME and $NAMESPACE with your values):

    helm upgrade -i $RELEASENAME -n $NAMESPACE docker-selenium/selenium-grid \
        --set tls.enabled=true \
        --set-file tls.secretFiles.tls\.crt=/path/to/your/tls.crt \
        --set-file tls.secretFiles.tls\.key=/path/to/your/tls.key \
        --set-file tls.secretFiles.server\.jks=/path/to/your/server.jks
  3. Create your own TLS Secret with your certificate, private key, truststore and pass the Secret name via tls.nameOverride when deploying the chart. For example (replace $RELEASENAME and $NAMESPACE with your values):

    # Steps to prepare your self-signed certificate
    ./certs/cert.sh -d /path/to/your/
    # Create TLS Secret with your certificate, private key, truststore
    kubectl create secret generic -n $NAMESPACE my-external-tls-secret \
        --from-file=tls.crt=/path/to/your/tls.crt \
        --from-file=tls.key=/path/to/your/tls.key \
        --from-file=server.jks=/path/to/your/server.jks
    # Deploy chart with your external TLS Secret
    helm upgrade -i $RELEASENAME -n $NAMESPACE docker-selenium/selenium-grid \
        --set tls.enabled=true --set tls.nameOverride=my-external-tls-secret

    In case your external secret contains key file names are different with default, you can instruct server to use them via following values:

    tls:
      enabled: true
      nameOverride: my-external-tls-secret
      certificateFile: "my-tls.crt"
      privateKeyFile: "my-tls.key"
      trustStoreFile: "my-server.jks"
      trustStorePassword: "mytruststorepassword"

Secure Connection to Selenium Grid components

When enabling secure communication between Selenium Grid server components, you need to set the following values:

tls:
  enabled: true

In additional, if the ingress is enabled, and approach SSL Passthrough is used to ensure the request forwards to the backend components via an encrypted connection.
With ingress.hostname is set, the default server TLS secret is also used for hosts TLS secretName when ingress.tls is empty. Once you specify ingress.tls, your specified secret will be used for hosts TLS secretName.

SeleniumGrid_TLS_SSL-Passthrough

Moreover, when sub-chart ingress-nginx is enabled (deploy Ingress NGINX Controller together), the default server TLS secret can also be assigned via ingress-nginx.controller.extraArgs.default-ssl-certificate.
For example (replace $RELEASENAME and $NAMESPACE with your values):

helm upgrade -i $RELEASENAME -n $NAMESPACE docker-selenium/selenium-grid \
    --set tls.enabled=true \
    --set ingress-nginx.enabled=true \
    --set ingress-nginx.controller.extraArgs.default-ssl-certificate=$NAMESPACE/$RELEASENAME-selenium-tls-secret

Below is an example of Grid UI accessible via NodePort with secure connection, and using external TLS Secret (replace $RELEASENAME and $NAMESPACE with your values):

helm upgrade -i $RELEASENAME -n $NAMESPACE docker-selenium/selenium-grid \
  --set ingress.enabled=false \
  --set isolateComponents=true \
  --set components.router.serviceType=NodePort \
  --set tls.enabled=true \
  --set tls.nameOverride=my-external-tls-secret

Grid UI can be accessed via HTTPS address https://your.host.public.ip:30444.

SeleniumGrid_TLS_WithoutProxy

Secure Connection to the Ingress proxy

When enabling secure communication via HTTPS/TLS between the client and the Ingress proxy only (SSL Offloading / aka SSL Termination). The proxy will terminate the TLS connection, decrypt incoming HTTPS traffic and send it to the backend components without encryption. The backend Selenium Grid components doesn't need to understand HTTPS. To enable this mode, you need to set the following values:

tls:
  ingress:
    enabled: true

SeleniumGrid_TLS_SSL-Termination

In additional, a self-signed certificate and private key can be generated runtime during the chart deployment for Ingress TLS by setting these values (replace $RELEASENAME with your value):

tls:
  ingress:
    generateTLS: true
    defaultName: "MySelfSignedCert"
    defaultDays: 3650
    defaultCN: "www.domain.com" # Common Name
    defaultSANList:
      - selenium-grid.prod.domain.com # Subject Alternative Name
      - selenium-grid.staging.domain.com
    defaultIPList:
      - 10.87.99.100 # Public IP of the host running K8s or LoadBalancer IP
      - 10.87.100.101

ingress-ngnix:
  enabled: true
  controller:
    extraArgs:
      default-ssl-certificate: $(POD_NAMESPACE)/$RELEASENAME-selenium-tls-secret

You can get the tls.crt and tls.key from the Secret after the chart is deployed. For example (replace $RELEASENAME and $NAMESPACE with your values):

kubectl get secret $RELEASENAME-selenium-tls-secret -n $NAMESPACE -o jsonpath="{.data.tls\.crt}" | base64 -d > ./tls.crt
kubectl get secret $RELEASENAME-selenium-tls-secret -n $NAMESPACE -o jsonpath="{.data.tls\.key}" | base64 -d > ./tls.key

Below is an example of Grid UI accessible via secure connection to the Ingress proxy with self-signed certificate in external TLS Secret (replace $RELEASENAME and $NAMESPACE with your values):

helm upgrade -i $RELEASENAME -n $NAMESPACE docker-selenium/selenium-grid \
  --set ingress.enabled=true \
  --set ingress.hostname="selenium-grid.prod.domain.com" \
  --set tls.ingress.enabled=true \
  --set tls.nameOverride=my-external-tls-secret \
  --set ingress-nginx.enabled=true \
  --set ingress-nginx.controller.extraArgs.default-ssl-certificate=$NAMESPACE/my-external-tls-secret

Grid UI can be accessed via HTTPS address https://selenium-grid.prod.domain.com.

Node Registration

To enable secure in the node registration to make sure that the node is one you control and not a rouge node, you can enable and provide a registration secret string to Distributor, Router and
Node servers in config registrationSecret. For example:

registrationSecret:
  enabled: true
  value: "matchThisSecret"

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • I have read the contributing document.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

PR Type

enhancement, tests, documentation


Description

  • Enhanced cert.sh script to support new options and improved file handling.
  • Simplified Minikube startup command in test setup.
  • Updated test scripts to handle secure connection configurations.
  • Updated certificate file extensions and paths in various scripts.
  • Added and updated helper functions for secure connection settings.
  • Added new arguments and updated classpath fetch in Dockerfile.
  • Added new test targets and updated existing ones for secure connections.
  • Updated documentation for secure communication and TLS secrets.
  • Removed old self-signed certificate and private key files.
  • Added new self-signed certificate and private key files.
  • Updated deployment templates to handle secure connections.

Changes walkthrough 📝

Relevant files
Enhancement
8 files
cert.sh
Enhance certificate generation script with new options and cleanup

charts/selenium-grid/certs/cert.sh

  • Added command-line options for specifying directory path.
  • Introduced new environment variables and improved IP address handling.
  • Enhanced file handling and cleanup processes.
  • Improved base64 encoding logic and file renaming.
  • +75/-24 
    _helpers.tpl
    Add and update helper functions for secure connections     

    charts/selenium-grid/templates/_helpers.tpl

  • Added new helper functions for secure connection settings.
  • Updated existing functions to use new secure connection logic.
  • +64/-21 
    Dockerfile
    Add Netty version argument and update classpath fetch       

    Base/Dockerfile

  • Added new argument for Netty version.
  • Updated classpath fetch command to include Netty codec.
  • +2/-1     
    tls.crt
    Add new self-signed certificate file                                         

    charts/selenium-grid/certs/tls.crt

    • Added new self-signed certificate file.
    +24/-0   
    tls.key
    Add new private key file                                                                 

    charts/selenium-grid/certs/tls.key

    • Added new private key file.
    +28/-0   
    distributor-deployment.yaml
    Update distributor deployment for secure connections         

    charts/selenium-grid/templates/distributor-deployment.yaml

  • Updated volume mounts and secret references for secure connections.
  • +3/-3     
    event-bus-deployment.yaml
    Update event bus deployment for secure connections             

    charts/selenium-grid/templates/event-bus-deployment.yaml

  • Updated volume mounts and secret references for secure connections.
  • +3/-3     
    hub-deployment.yaml
    Update hub deployment for secure connections                         

    charts/selenium-grid/templates/hub-deployment.yaml

  • Updated volume mounts and secret references for secure connections.
  • +4/-3     
    Tests
    4 files
    chart_cluster_setup.sh
    Simplify Minikube startup command in test setup                   

    tests/charts/make/chart_cluster_setup.sh

  • Removed resource limitation settings for Minikube.
  • Simplified Minikube startup command.
  • +1/-12   
    chart_test.sh
    Update test script for secure connection configurations   

    tests/charts/make/chart_test.sh

  • Updated certificate paths and secret names.
  • Added new Helm command options for secure connections.
  • Improved cleanup and setup logic for secure connections.
  • +64/-6   
    bootstrap.sh
    Update certificate file extensions in bootstrap script     

    tests/customCACert/bootstrap.sh

    • Updated certificate file extensions from .pem to .crt.
    +2/-2     
    Makefile
    Add and update test targets for secure connections             

    Makefile

  • Added new test targets for secure connection configurations.
  • Updated existing test targets to include secure connection options.
  • +12/-6   
    Documentation
    1 files
    README.md
    Update documentation for secure communication and TLS secrets

    charts/selenium-grid/README.md

  • Updated documentation for secure communication configuration.
  • Added new sections for creating TLS secrets and secure connections.
  • +128/-44
    Miscellaneous
    2 files
    selenium.pem
    Remove old self-signed certificate file                                   

    charts/selenium-grid/certs/selenium.pem

    • Removed old self-signed certificate file.
    +0/-24   
    selenium.pkcs8.base64
    Remove old base64 encoded private key file                             

    charts/selenium-grid/certs/selenium.pkcs8.base64

    • Removed old base64 encoded private key file.
    +0/-1     

    💡 PR-Agent usage:
    Comment /help on the PR to get a list of all available PR-Agent tools and their descriptions

    Copy link

    PR Reviewer Guide 🔍

    ⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
    🧪 No relevant tests
    🔒 Security concerns

    No specific security vulnerabilities are introduced in the PR code itself, but the script modifications in 'cert.sh' could potentially be exploited if not handled carefully, especially regarding file operations and external command executions.

    ⚡ Key issues to review

    Script Robustness
    The script lacks robust error handling for file operations and external command failures (e.g., keytool, openssl). Consider adding checks after each critical operation to ensure the script exits with an appropriate error message and status if something goes wrong.

    Security Concern
    The script uses predictable names for temporary files which can lead to race conditions or security vulnerabilities if multiple instances of the script run concurrently. Consider using mktemp or similar to create secure, unique temporary file names.

    Code Duplication
    The logic to determine if the server connection should be secure is duplicated in multiple template definitions. Consider refactoring this into a single reusable template function or variable.

    Copy link

    PR Code Suggestions ✨

    CategorySuggestion                                                                                                                                    Score
    Security
    Handle potential unset configuration for hostname verification explicitly

    To avoid potential security issues and ensure that the configuration is explicit,
    consider handling the case where $.Values.tls.disableHostnameVerification might not
    be set, by providing a default value.

    charts/selenium-grid/templates/_helpers.tpl [625]

    -{{- $unsafeSsl := printf "%s" (ternary "true" "false" .Values.tls.disableHostnameVerification) -}}
    +{{- $unsafeSsl := printf "%s" (ternary "true" "false" (default false .Values.tls.disableHostnameVerification)) -}}
     
    • Apply this suggestion
    Suggestion importance[1-10]: 10

    Why: Providing a default value for $.Values.tls.disableHostnameVerification ensures that the configuration is explicit, avoiding potential security issues.

    10
    Enhance security by not hardcoding sensitive information like passwords

    Instead of hardcoding the trustStore password, consider using a more secure approach
    by fetching it from a secret management service or environment variable.

    charts/selenium-grid/values.yaml [56]

    -trustStorePassword: "seleniumkeystore"
    +trustStorePassword: {{ .Values.tls.trustStorePassword | quote }}
     
    • Apply this suggestion
    Suggestion importance[1-10]: 10

    Why: Fetching the trustStore password from a values file instead of hardcoding it enhances security by preventing sensitive information from being exposed in the codebase.

    10
    Best practice
    Add error handling to exit the script on any command failure

    To ensure that the script handles errors properly and exits when a command fails,
    consider adding set -e at the beginning of the script. This command causes the
    script to exit immediately if a command exits with a non-zero status.

    charts/selenium-grid/certs/cert.sh [1]

     #!/bin/bash
    +set -e
     
    • Apply this suggestion
    Suggestion importance[1-10]: 9

    Why: Adding set -e at the beginning of the script is a best practice for bash scripts to ensure that the script exits immediately if any command fails, preventing potential issues from cascading errors.

    9
    Add robustness to protocol determination logic

    It's a good practice to ensure that the protocol switching logic is robust against
    potential configuration errors. Adding a default case or validation can prevent
    runtime issues if the configuration is not set correctly.

    charts/selenium-grid/templates/_helpers.tpl [19]

    -{{- (eq (include "seleniumGrid.server.secureConnection" $) "true") | ternary "https" "http" -}}
    +{{- $protocol := "http" -}}
    +{{- if eq (include "seleniumGrid.server.secureConnection" $) "true" -}}
    +{{- $protocol = "https" -}}
    +{{- end -}}
    +{{- $protocol -}}
     
    • Apply this suggestion
    Suggestion importance[1-10]: 9

    Why: Adding a default case or validation in the protocol determination logic ensures robustness against potential configuration errors, preventing runtime issues.

    9
    Use variable configuration instead of hardcoded values for conditions

    Replace the hardcoded "true" string with a reference to the .Values.tls.enabled to
    maintain flexibility and consistency with other configuration options.

    charts/selenium-grid/templates/distributor-deployment.yaml [86]

    -{{- if eq (include "seleniumGrid.server.secureConnection" $) "true" }}
    +{{- if .Values.tls.enabled }}
     
    • Apply this suggestion
    Suggestion importance[1-10]: 9

    Why: Replacing the hardcoded "true" with .Values.tls.enabled enhances flexibility and consistency with other configuration options, following best practices.

    9
    Ensure explicit default value handling in ternary operations

    Consider adding a default value for the ternary operation to ensure that the value
    is explicitly set when $.Values.tls.enabled is false. This can prevent unintended
    behavior in configurations where TLS is not enabled.

    charts/selenium-grid/templates/_helpers.tpl [5]

    -{{- $.Values.tls.enabled | ternary "true" "" -}}
    +{{- $.Values.tls.enabled | ternary "true" "false" -}}
     
    • Apply this suggestion
    Suggestion importance[1-10]: 8

    Why: Adding an explicit default value in the ternary operation improves code robustness by preventing unintended behavior when $.Values.tls.enabled is false.

    8
    Use the configured volume mount path from the values file to ensure consistency

    Ensure that the volume mount path for TLS certificates is fetched from the values
    file to maintain consistency across templates.

    charts/selenium-grid/templates/hub-deployment.yaml [147]

    +mountPath: {{ .Values.tls.certVolumeMountPath | quote }}
     
    -
    • Apply this suggestion
    Suggestion importance[1-10]: 8

    Why: Ensuring that the volume mount path for TLS certificates is fetched from the values file maintains consistency across templates and makes the configuration more flexible and easier to manage.

    8
    Possible bug
    Improve the condition check for empty values to handle unexpected types

    Use a more specific condition to check for empty values in the secret files to avoid
    potential issues with unexpected null or false values.

    charts/selenium-grid/templates/tls-cert-secret.yaml [28]

    -{{- if empty $value }}
    +{{- if eq $value "" }}
     
    • Apply this suggestion
    Suggestion importance[1-10]: 9

    Why: Using a more specific condition to check for empty values avoids potential issues with unexpected null or false values, which can prevent bugs and improve the robustness of the code.

    9
    Possible issue
    Validate that the directory path is not empty after parsing options

    The script should validate that the DIRECTORY_PATH is not empty after parsing
    options, to ensure that a valid directory is provided for storing certificates. This
    can prevent potential issues where no directory is specified, leading to unexpected
    behavior.

    charts/selenium-grid/certs/cert.sh [9-12]

     case ${opt} in
       d )
         DIRECTORY_PATH=$OPTARG
    +    if [ -z "$DIRECTORY_PATH" ]; then
    +      echo "Error: Directory path must not be empty."
    +      exit 1
    +    fi
         ;;
     
    • Apply this suggestion
    Suggestion importance[1-10]: 8

    Why: Validating that the DIRECTORY_PATH is not empty after parsing options ensures that a valid directory is provided, preventing potential issues where no directory is specified, leading to unexpected behavior.

    8
    Add error handling for the file deletion command to ensure it succeeds

    It's recommended to check if the rm command successfully deletes the files before
    proceeding. This can be done by checking the exit status of the rm command. If the
    deletion fails, the script should handle this gracefully, possibly by logging an
    error and exiting.

    charts/selenium-grid/certs/cert.sh [36]

    -rm -f ${CERTNAME}.* ${SERVER_KEYSTORE}
    +if ! rm -f ${CERTNAME}.* ${SERVER_KEYSTORE}; then
    +  echo "Failed to remove existing files."
    +  exit 1
    +fi
     
    • Apply this suggestion
    Suggestion importance[1-10]: 8

    Why: Checking if the rm command successfully deletes the files before proceeding ensures that the script handles any failure gracefully, preventing further operations on non-deleted files.

    8
    Conditionally include the secretName attribute to avoid configuration errors

    Ensure that the secretName attribute is included within the conditional block to
    prevent potential issues when the condition for secure connection is not met.

    charts/selenium-grid/templates/distributor-deployment.yaml [176]

    -secretName: {{ include "seleniumGrid.tls.fullname" .  | quote }}
    +{{- if eq (include "seleniumGrid.server.secureConnection" $) "true" }}
    +    secretName: {{ include "seleniumGrid.tls.fullname" .  | quote }}
    +{{- end }}
     
    • Apply this suggestion
    Suggestion importance[1-10]: 8

    Why: This suggestion prevents potential issues by ensuring the secretName attribute is only included when the secure connection condition is met, improving reliability.

    8
    Handle potential multiple IP addresses returned by hostname -I

    The script uses hostname -I which can potentially list multiple IP addresses. It's
    safer to explicitly handle the possibility of multiple IPs by either documenting the
    expected behavior or modifying the script to handle multiple IPs appropriately.

    charts/selenium-grid/certs/cert.sh [30]

    -ADD_IP_ADDRESS=",IP:$(hostname -I | awk '{print $1}')"
    +IP_ADDRESSES=$(hostname -I | awk '{print $1}')
    +if [[ $(echo $IP_ADDRESSES | wc -w) -gt 1 ]]; then
    +  echo "Error: Multiple IP addresses found. Please specify which one to use."
    +  exit 1
    +fi
    +ADD_IP_ADDRESS=",IP:$IP_ADDRESSES"
     
    • Apply this suggestion
    Suggestion importance[1-10]: 7

    Why: Handling the possibility of multiple IP addresses returned by hostname -I ensures that the script does not proceed with ambiguous IP addresses, which could lead to unexpected behavior.

    7
    Maintainability
    Use a variable for the common port to enhance maintainability

    Consider using a variable for the common port 443 used in multiple targets to ensure
    consistency and simplify future changes.

    Makefile [766]

    -SECURE_INGRESS_ONLY_GENERATE=true SELENIUM_GRID_PROTOCOL=https SELENIUM_GRID_HOST=$$(hostname -i) SELENIUM_GRID_PORT=443
    +SECURE_INGRESS_ONLY_GENERATE=true SELENIUM_GRID_PROTOCOL=https SELENIUM_GRID_HOST=$$(hostname -i) SELENIUM_GRID_PORT=$(COMMON_PORT)
     
    • Apply this suggestion
    Suggestion importance[1-10]: 8

    Why: Using a variable for the common port 443 improves maintainability by ensuring consistency and simplifying future changes. This is a good practice for managing configuration values that are used in multiple places.

    8
    Use helper variables for complex ternary operations to enhance readability

    To improve the readability and maintainability of the code, consider using a helper
    variable to store the result of the complex ternary operation. This will make the
    code easier to understand and modify in the future.

    charts/selenium-grid/templates/_helpers.tpl [12]

    -{{- or $.Values.tls.enabled $.Values.tls.ingress.enabled $.Values.tls.ingress.generateTLS | ternary "true" "" -}}
    +{{- $isTlsEnabled := or $.Values.tls.enabled $.Values.tls.ingress.enabled $.Values.tls.ingress.generateTLS -}}
    +{{- $isTlsEnabled | ternary "true" "" -}}
     
    • Apply this suggestion
    Suggestion importance[1-10]: 7

    Why: Using a helper variable for the complex ternary operation enhances readability and maintainability, making the code easier to understand and modify.

    7
    Ensure the readOnly attribute is conditionally applied

    Ensure that the readOnly attribute is included within the conditional block to
    maintain consistency and avoid potential misconfigurations when the condition is not
    met.

    charts/selenium-grid/templates/distributor-deployment.yaml [89]

    -readOnly: true
    +{{- if eq (include "seleniumGrid.server.secureConnection" $) "true" }}
    +    readOnly: true
    +{{- end }}
     
    • Apply this suggestion
    Suggestion importance[1-10]: 7

    Why: This suggestion improves maintainability by ensuring the readOnly attribute is only applied when the secure connection condition is met, preventing potential misconfigurations.

    7
    Enhancement
    Improve readability and maintainability by using descriptive variable names

    Use a more descriptive variable name for the condition to enable secure connections,
    enhancing readability and maintainability.

    charts/selenium-grid/templates/distributor-deployment.yaml [86]

    -{{- if eq (include "seleniumGrid.server.secureConnection" $) "true" }}
    +{{- if .Values.enableSecureConnection }}
     
    • Apply this suggestion
    Suggestion importance[1-10]: 6

    Why: Using a more descriptive variable name enhances readability and maintainability, although it is a minor improvement compared to functional changes.

    6

    @VietND96 VietND96 merged commit 4cc2038 into SeleniumHQ:trunk Jul 16, 2024
    28 checks passed
    @VietND96 VietND96 added this to the 4.23 milestone Jul 17, 2024
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    None yet
    Development

    Successfully merging this pull request may close these issues.

    [🚀 Feature]: Manage TLS Certificate Externally
    1 participant