From 3ef585a4638687f3b37077bda1c84b1d9d0e1a60 Mon Sep 17 00:00:00 2001 From: Pwd9000-ML Date: Wed, 24 Jan 2024 10:21:41 +0000 Subject: [PATCH 1/8] update with violations --- 06_librechat_app.tf | 2 +- 06_librechat_app_config.tf | 27 +++++++++++++++ tests/auto_test1/main.tf | 3 ++ tests/auto_test1/testing.auto.tfvars | 24 +++++++++++++ tests/auto_test1/variables.tf | 50 ++++++++++++++++++++++++++++ variables.tf | 50 ++++++++++++++++++++++++++++ 6 files changed, 155 insertions(+), 1 deletion(-) diff --git a/06_librechat_app.tf b/06_librechat_app.tf index a2848b2..fb3d40b 100644 --- a/06_librechat_app.tf +++ b/06_librechat_app.tf @@ -164,7 +164,7 @@ resource "azurerm_app_service_custom_hostname_binding" "hostname_binding" { app_service_name = var.libre_app_name resource_group_name = azurerm_resource_group.az_openai_rg.name - depends_on = [azurerm_dns_cname_record.cname_record, azurerm_linux_web_app.librechat ] + depends_on = [azurerm_dns_cname_record.cname_record, azurerm_linux_web_app.librechat] } resource "azurerm_app_service_managed_certificate" "libre_app_cert" { diff --git a/06_librechat_app_config.tf b/06_librechat_app_config.tf index 54227a2..f16d00e 100644 --- a/06_librechat_app_config.tf +++ b/06_librechat_app_config.tf @@ -60,6 +60,33 @@ locals { REFRESH_TOKEN_EXPIRY = (1000 * 60 * 60 * 24) * 5 #7 days JWT_SECRET = var.libre_app_jwt_secret != null ? var.libre_app_jwt_secret : "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.libre_app_jwt_secret.id})" JWT_REFRESH_SECRET = var.libre_app_jwt_refresh_secret != null ? var.libre_app_jwt_refresh_secret : "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.libre_app_jwt_refresh_secret.id})" + + ### User Violations ### + BAN_VIOLATIONS = var.violations.enabled + BAN_DURATION = var.violations.ban_duration + BAN_INTERVAL = var.violations.ban_interval + + LOGIN_VIOLATION_SCORE = var.violations.login_violation_score + REGISTRATION_VIOLATION_SCORE = var.violations.registration_violation_score + CONCURRENT_VIOLATION_SCORE = var.violations.concurrent_violation_score + MESSAGE_VIOLATION_SCORE = var.violations.message_violation_score + NON_BROWSER_VIOLATION_SCORE = var.violations.non_browser_violation_score + + LOGIN_MAX = var.violations.login_max + LOGIN_WINDOW = var.violations.login_window + REGISTER_MAX = var.violations.register_max + REGISTER_WINDOW = var.violations.register_window + + LIMIT_CONCURRENT_MESSAGES = var.violations.limit_concurrent_messages + CONCURRENT_MESSAGE_MAX = var.violations.concurrent_message_max + + LIMIT_MESSAGE_IP = var.violations.limit_message_ip + MESSAGE_IP_MAX = var.violations.message_ip_max + MESSAGE_IP_WINDOW = var.violations.message_ip_window + + LIMIT_MESSAGE_USER = var.violations.limit_message_user + MESSAGE_USER_MAX = var.violations.message_user_max + MESSAGE_USER_WINDOW = var.violations.message_user_window } } diff --git a/tests/auto_test1/main.tf b/tests/auto_test1/main.tf index 8bedbd6..8badf14 100644 --- a/tests/auto_test1/main.tf +++ b/tests/auto_test1/main.tf @@ -145,6 +145,9 @@ module "private-chatgpt-openai" { libre_app_jwt_secret = var.libre_app_jwt_secret libre_app_jwt_refresh_secret = var.libre_app_jwt_refresh_secret + # Violations + violations = var.violations + # Custom Domain and Managed Certificate (Optional) libre_app_custom_domain_create = var.libre_app_custom_domain_create librechat_app_custom_domain_name = "${var.librechat_app_custom_domain_name}${random_integer.number.result}" diff --git a/tests/auto_test1/testing.auto.tfvars b/tests/auto_test1/testing.auto.tfvars index 8606dde..87f55ab 100644 --- a/tests/auto_test1/testing.auto.tfvars +++ b/tests/auto_test1/testing.auto.tfvars @@ -177,6 +177,30 @@ libre_app_allow_social_registration = false libre_app_jwt_secret = null libre_app_jwt_refresh_secret = null +# violations +violations = { + enabled = false + ban_duration = 1000 * 60 * 60 * 2 + ban_interval = 20 + login_violation_score = 1 + registration_violation_score = 1 + concurrent_violation_score = 1 + message_violation_score = 1 + non_browser_violation_score = 20 + login_max = 7 + login_window = 5 + register_max = 5 + register_window = 60 + limit_concurrent_messages = false + concurrent_message_max = 2 + limit_message_ip = false + message_ip_max = 40 + message_ip_window = 1 + limit_message_user = false + message_user_max = 40 + message_user_window = 1 +} + # Custom Domain and Managed Certificate (Optional) libre_app_custom_domain_create = true librechat_app_custom_domain_name = "privategpt" diff --git a/tests/auto_test1/variables.tf b/tests/auto_test1/variables.tf index cc2399e..706ac03 100644 --- a/tests/auto_test1/variables.tf +++ b/tests/auto_test1/variables.tf @@ -639,6 +639,56 @@ variable "libre_app_jwt_refresh_secret" { sensitive = true } +# Violations + +variable "violations" { + description = "Configuration for violations" + type = object({ + enabled = bool + ban_duration = number + ban_interval = number + login_violation_score = number + registration_violation_score = number + concurrent_violation_score = number + message_violation_score = number + non_browser_violation_score = number + login_max = number + login_window = number + register_max = number + register_window = number + limit_concurrent_messages = bool + concurrent_message_max = number + limit_message_ip = bool + message_ip_max = number + message_ip_window = number + limit_message_user = bool + message_user_max = number + message_user_window = number + }) + default = { + enabled = true + ban_duration = 1000 * 60 * 60 * 2 + ban_interval = 20 + login_violation_score = 1 + registration_violation_score = 1 + concurrent_violation_score = 1 + message_violation_score = 1 + non_browser_violation_score = 20 + login_max = 7 + login_window = 5 + register_max = 5 + register_window = 60 + limit_concurrent_messages = true + concurrent_message_max = 2 + limit_message_ip = true + message_ip_max = 40 + message_ip_window = 1 + limit_message_user = false + message_user_max = 40 + message_user_window = 1 + } +} + # Custom Domain and Managed Certificate (Optional) variable "libre_app_custom_domain_create" { diff --git a/variables.tf b/variables.tf index bbf9c7c..a9efb70 100644 --- a/variables.tf +++ b/variables.tf @@ -646,6 +646,56 @@ variable "libre_app_jwt_refresh_secret" { sensitive = true } +# Violations + +variable "violations" { + description = "Configuration for violations" + type = object({ + enabled = bool + ban_duration = number + ban_interval = number + login_violation_score = number + registration_violation_score = number + concurrent_violation_score = number + message_violation_score = number + non_browser_violation_score = number + login_max = number + login_window = number + register_max = number + register_window = number + limit_concurrent_messages = bool + concurrent_message_max = number + limit_message_ip = bool + message_ip_max = number + message_ip_window = number + limit_message_user = bool + message_user_max = number + message_user_window = number + }) + default = { + enabled = true + ban_duration = 1000 * 60 * 60 * 2 + ban_interval = 20 + login_violation_score = 1 + registration_violation_score = 1 + concurrent_violation_score = 1 + message_violation_score = 1 + non_browser_violation_score = 20 + login_max = 7 + login_window = 5 + register_max = 5 + register_window = 60 + limit_concurrent_messages = true + concurrent_message_max = 2 + limit_message_ip = true + message_ip_max = 40 + message_ip_window = 1 + limit_message_user = false + message_user_max = 40 + message_user_window = 1 + } +} + # Custom Domain and Managed Certificate (Optional) variable "libre_app_custom_domain_create" { From 730f66c3622c9c31c79d2c8918df2140361b6650 Mon Sep 17 00:00:00 2001 From: Pwd9000-ML Date: Wed, 24 Jan 2024 10:25:34 +0000 Subject: [PATCH 2/8] Add violations --- 06_librechat_app_config.tf | 38 ++++++++++++++-------------- tests/auto_test1/main.tf | 2 +- tests/auto_test1/testing.auto.tfvars | 2 +- tests/auto_test1/variables.tf | 4 +-- variables.tf | 2 +- 5 files changed, 23 insertions(+), 25 deletions(-) diff --git a/06_librechat_app_config.tf b/06_librechat_app_config.tf index f16d00e..fc7a278 100644 --- a/06_librechat_app_config.tf +++ b/06_librechat_app_config.tf @@ -62,31 +62,31 @@ locals { JWT_REFRESH_SECRET = var.libre_app_jwt_refresh_secret != null ? var.libre_app_jwt_refresh_secret : "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.libre_app_jwt_refresh_secret.id})" ### User Violations ### - BAN_VIOLATIONS = var.violations.enabled - BAN_DURATION = var.violations.ban_duration - BAN_INTERVAL = var.violations.ban_interval + BAN_VIOLATIONS = var.libre_app_violations.enabled + BAN_DURATION = var.libre_app_violations.ban_duration + BAN_INTERVAL = var.libre_app_violations.ban_interval - LOGIN_VIOLATION_SCORE = var.violations.login_violation_score - REGISTRATION_VIOLATION_SCORE = var.violations.registration_violation_score - CONCURRENT_VIOLATION_SCORE = var.violations.concurrent_violation_score - MESSAGE_VIOLATION_SCORE = var.violations.message_violation_score - NON_BROWSER_VIOLATION_SCORE = var.violations.non_browser_violation_score + LOGIN_VIOLATION_SCORE = var.libre_app_violations.login_violation_score + REGISTRATION_VIOLATION_SCORE = var.libre_app_violations.registration_violation_score + CONCURRENT_VIOLATION_SCORE = var.libre_app_violations.concurrent_violation_score + MESSAGE_VIOLATION_SCORE = var.libre_app_violations.message_violation_score + NON_BROWSER_VIOLATION_SCORE = var.libre_app_violations.non_browser_violation_score - LOGIN_MAX = var.violations.login_max - LOGIN_WINDOW = var.violations.login_window - REGISTER_MAX = var.violations.register_max - REGISTER_WINDOW = var.violations.register_window + LOGIN_MAX = var.libre_app_violations.login_max + LOGIN_WINDOW = var.libre_app_violations.login_window + REGISTER_MAX = var.libre_app_violations.register_max + REGISTER_WINDOW = var.libre_app_violations.register_window - LIMIT_CONCURRENT_MESSAGES = var.violations.limit_concurrent_messages - CONCURRENT_MESSAGE_MAX = var.violations.concurrent_message_max + LIMIT_CONCURRENT_MESSAGES = var.libre_app_violations.limit_concurrent_messages + CONCURRENT_MESSAGE_MAX = var.libre_app_violations.concurrent_message_max - LIMIT_MESSAGE_IP = var.violations.limit_message_ip - MESSAGE_IP_MAX = var.violations.message_ip_max + LIMIT_MESSAGE_IP = var.libre_app_violations.limit_message_ip + MESSAGE_IP_MAX = var.libre_app_violations.message_ip_max MESSAGE_IP_WINDOW = var.violations.message_ip_window - LIMIT_MESSAGE_USER = var.violations.limit_message_user - MESSAGE_USER_MAX = var.violations.message_user_max - MESSAGE_USER_WINDOW = var.violations.message_user_window + LIMIT_MESSAGE_USER = var.libre_app_violations.limit_message_user + MESSAGE_USER_MAX = var.libre_app_violations.message_user_max + MESSAGE_USER_WINDOW = var.libre_app_violations.message_user_window } } diff --git a/tests/auto_test1/main.tf b/tests/auto_test1/main.tf index 8badf14..d3f34f7 100644 --- a/tests/auto_test1/main.tf +++ b/tests/auto_test1/main.tf @@ -146,7 +146,7 @@ module "private-chatgpt-openai" { libre_app_jwt_refresh_secret = var.libre_app_jwt_refresh_secret # Violations - violations = var.violations + libre_app_violations = var.libre_app_violations # Custom Domain and Managed Certificate (Optional) libre_app_custom_domain_create = var.libre_app_custom_domain_create diff --git a/tests/auto_test1/testing.auto.tfvars b/tests/auto_test1/testing.auto.tfvars index 87f55ab..9caa702 100644 --- a/tests/auto_test1/testing.auto.tfvars +++ b/tests/auto_test1/testing.auto.tfvars @@ -178,7 +178,7 @@ libre_app_jwt_secret = null libre_app_jwt_refresh_secret = null # violations -violations = { +libre_app_violations = { enabled = false ban_duration = 1000 * 60 * 60 * 2 ban_interval = 20 diff --git a/tests/auto_test1/variables.tf b/tests/auto_test1/variables.tf index 706ac03..b591fc5 100644 --- a/tests/auto_test1/variables.tf +++ b/tests/auto_test1/variables.tf @@ -640,8 +640,7 @@ variable "libre_app_jwt_refresh_secret" { } # Violations - -variable "violations" { +variable "libre_app_violations" { description = "Configuration for violations" type = object({ enabled = bool @@ -690,7 +689,6 @@ variable "violations" { } # Custom Domain and Managed Certificate (Optional) - variable "libre_app_custom_domain_create" { type = bool description = "Create a custom domain and managed certificate for the App Service." diff --git a/variables.tf b/variables.tf index a9efb70..f169575 100644 --- a/variables.tf +++ b/variables.tf @@ -648,7 +648,7 @@ variable "libre_app_jwt_refresh_secret" { # Violations -variable "violations" { +variable "libre_app_violations" { description = "Configuration for violations" type = object({ enabled = bool From b69ed738f41010ca9e07d25805d53cead9df8ee2 Mon Sep 17 00:00:00 2001 From: Pwd9000-ML Date: Wed, 24 Jan 2024 10:38:58 +0000 Subject: [PATCH 3/8] fix --- 06_librechat_app_config.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/06_librechat_app_config.tf b/06_librechat_app_config.tf index fc7a278..f5bb56a 100644 --- a/06_librechat_app_config.tf +++ b/06_librechat_app_config.tf @@ -82,7 +82,7 @@ locals { LIMIT_MESSAGE_IP = var.libre_app_violations.limit_message_ip MESSAGE_IP_MAX = var.libre_app_violations.message_ip_max - MESSAGE_IP_WINDOW = var.violations.message_ip_window + MESSAGE_IP_WINDOW = var.libre_app_violations.message_ip_window LIMIT_MESSAGE_USER = var.libre_app_violations.limit_message_user MESSAGE_USER_MAX = var.libre_app_violations.message_user_max From 1e6bb413aec1a1238959377ebb6f973664a67227 Mon Sep 17 00:00:00 2001 From: Pwd9000-ML Date: Wed, 24 Jan 2024 10:44:23 +0000 Subject: [PATCH 4/8] update documentation --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a05cdfa..b43dc14 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,21 @@ New integrations and features have been added to the module to use the latest ** ## Coming next - Feature development -- [x] Custom domain support with managed certificates (Released in v2.1.0) +### V2.3.x + - [ ] Privatise solution with Private endpoint support - [ ] Front Door and WAF support for public entrypoint - [ ] Azure AI Search/MeiliSearch Integration - [ ] Add additional support for [Azure OpenAI DALL-E-3](https://docs.microsoft.com/en-us/azure/cognitive-services/openai-dall-e/overview) -- [ ] Updated documentation and examples + +### V2.2.x + +- [x] Added User Violations Support +- [x] Updated documentation and examples + +### V2.1.x + +- [x] Custom domain support with managed certificates ## Legacy Version 1.x From b2b1cb29955aa96a3ed97e9474039a43357ad718 Mon Sep 17 00:00:00 2001 From: Pwd9000-ML Date: Wed, 24 Jan 2024 10:52:11 +0000 Subject: [PATCH 5/8] up --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b43dc14..f879f78 100644 --- a/README.md +++ b/README.md @@ -11,19 +11,19 @@ New integrations and features have been added to the module to use the latest ** ## Coming next - Feature development -### V2.3.x +### v2.3.x - [ ] Privatise solution with Private endpoint support - [ ] Front Door and WAF support for public entrypoint - [ ] Azure AI Search/MeiliSearch Integration - [ ] Add additional support for [Azure OpenAI DALL-E-3](https://docs.microsoft.com/en-us/azure/cognitive-services/openai-dall-e/overview) -### V2.2.x +### v2.2.x - [x] Added User Violations Support - [x] Updated documentation and examples -### V2.1.x +### v2.1.x - [x] Custom domain support with managed certificates From 155ba007ebb6e42275bce0a9546cac8030920c1c Mon Sep 17 00:00:00 2001 From: Pwd9000-ML Date: Wed, 24 Jan 2024 11:32:17 +0000 Subject: [PATCH 6/8] update example --- .../README.md | 58 ++ .../common.auto.tfvars | 198 ++++++ .../main.tf | 144 ++++ .../variables.tf | 669 ++++++++++++++++++ 4 files changed, 1069 insertions(+) create mode 100644 examples/public_deployment_with_custom_domain/README.md create mode 100644 examples/public_deployment_with_custom_domain/common.auto.tfvars create mode 100644 examples/public_deployment_with_custom_domain/main.tf create mode 100644 examples/public_deployment_with_custom_domain/variables.tf diff --git a/examples/public_deployment_with_custom_domain/README.md b/examples/public_deployment_with_custom_domain/README.md new file mode 100644 index 0000000..de3a2a4 --- /dev/null +++ b/examples/public_deployment_with_custom_domain/README.md @@ -0,0 +1,58 @@ +# Example - Public Deployment with Custom Domain and IP Whitelisting + +This example contains a Terraform script for provisioning a set of Azure resources for a chat application powered by Azure OpenAI models. The script is designed to be modular and configurable, allowing you to customise the resources and settings to fit your specific needs. + +In this example the Chat App is deployed with a public endpoint, a custom domain name and managed certificate. The access to the app can be restricted to a set of whitelisted IP addresses or subnets using these variables: + +```hcl +libre_app_virtual_network_subnet_id = null # Access is allowed on the built in subnet of this module. If networking is created as part of the module, this will be automatically populated if value is 'null' (priority 100) +libre_app_allowed_subnets = null # Add any other subnet ids to allow access to the app service (optional) +libre_app_allowed_ip_addresses = [ + { + ip_address = "0.0.0.0/0" # (Change to your IP address or CIDR range) + priority = 200 + name = "ip-access-rule1" + action = "Allow" + } +] +``` + +## Resources + +The script provisions the following resources: + +- **Random Integer**: This is used to generate unique names for the resources to avoid naming conflicts. +- **Resource Group**: This is the resource group that contains all the resources for the chat application. +- **Virtual Network**: This is the virtual network and subnet that contains the chat application and supporting resources. +- **Key Vault**: This is used to store secrets for the chat application, such as the OpenAI API key and other secrets used by the application. +- **OpenAI Service**: This is the core service that powers the chat application. It uses OpenAI's GPT-3 model to generate responses to user inputs. +- **CosmosDB Instance**: This is the database for the chat application. It stores user data and chat logs. +- **App Service**: This hosts the chat application. It's configured with various settings for networking, storage, and access control. + +## Configuration + +The script uses variables for configuration. These variables can be set in a `common.auto.tfvars` file or passed in via the command line when running `terraform apply`. + +The variables include settings for the location, tags, resource group name, virtual network name, subnet configuration, key vault settings, OpenAI service settings, CosmosDB settings, and App Service settings. + +## Usage + +To use this script, you need to have Terraform installed. You can then clone this repository and run `terraform init` to initialize your Terraform workspace. Once the workspace is initialized, you can run `terraform apply` to create the resources. + +Please note that you will need to provide values for all the required variables in the `common.auto.tfvars` file and defining the variables there, or by passing them in via the command line when running `terraform apply`. + +## Contributing + +Contributions are welcome. Please submit a pull request if you have any improvements or fixes. Make sure to follow the existing code style and add comments to your code explaining what it does. + +## License + +This script is licensed under the MIT License. See the LICENSE file for more details. + +## Support + +If you encounter any issues or have any questions about this script, please open an issue on GitHub. We'll do our best to respond as quickly as possible. + +## Acknowledgements + +This script was developed by **Marcel Lupo** as part of a project to explore the capabilities of Azure OpenAI models. We'd like to thank the OpenAI and Microsoft team for their incredible work and ongoing support of the AI community. diff --git a/examples/public_deployment_with_custom_domain/common.auto.tfvars b/examples/public_deployment_with_custom_domain/common.auto.tfvars new file mode 100644 index 0000000..cd9621e --- /dev/null +++ b/examples/public_deployment_with_custom_domain/common.auto.tfvars @@ -0,0 +1,198 @@ +### 01 Common Variables + RG ### +resource_group_name = "Private-ChatGPT-OpenAI-Example" +location = "SwedenCentral" +tags = { + Terraform = "True" + Description = "Private ChatGPT hosted on Azure OpenAI (Librechat)" + Author = "Marcel Lupo" + GitHub = "https://github.com/Pwd9000-ML/terraform-azurerm-openai-private-chatgpt" +} + +### 02 networking ### +virtual_network_name = "demogptvnet" +vnet_address_space = ["10.3.0.0/24"] +subnet_config = { + subnet_name = "demogpt-sub" + subnet_address_space = ["10.3.0.0/24"] + service_endpoints = ["Microsoft.AzureCosmosDB", "Microsoft.Web", "Microsoft.KeyVault"] + private_endpoint_network_policies_enabled = false + private_link_service_network_policies_enabled = false + subnets_delegation_settings = { + app-service-plan = [ + { + name = "Microsoft.Web/serverFarms" + actions = ["Microsoft.Network/virtualNetworks/subnets/action"] + } + ] + } +} + +### 03 KeyVault ### +kv_name = "demogptkv" +kv_sku = "standard" +kv_fw_default_action = "Deny" +kv_fw_bypass = "AzureServices" +kv_fw_allowed_ips = ["0.0.0.0/0"] +kv_fw_network_subnet_ids = null + +### 04 Create OpenAI Service ### +oai_account_name = "demogptoai" +oai_sku_name = "S0" +oai_custom_subdomain_name = "demogptoai" +oai_dynamic_throttling_enabled = false +oai_fqdns = [] +oai_local_auth_enabled = true +oai_outbound_network_access_restricted = false +oai_public_network_access_enabled = true +oai_customer_managed_key = null +oai_identity = { + type = "SystemAssigned" +} +oai_network_acls = null +oai_storage = null +oai_model_deployment = [ + { + deployment_id = "gpt-35-turbo" + model_name = "gpt-35-turbo" + model_format = "OpenAI" + model_version = "1106" + scale_type = "Standard" + scale_capacity = 20 # 34K == Roughly 204 RPM (Requests per minute) + }, + { + deployment_id = "gpt-4" + model_name = "gpt-4" + model_format = "OpenAI" + model_version = "1106-Preview" + scale_type = "Standard" + scale_capacity = 20 + }, + { + deployment_id = "gpt-4-vision-preview" + model_name = "gpt-4" + model_format = "OpenAI" + model_version = "vision-preview" + scale_type = "Standard" + scale_capacity = 5 + }, + { + deployment_id = "dall-e-3" + model_name = "dall-e-3" + model_format = "OpenAI" + model_version = "3.0" + scale_type = "Standard" + scale_capacity = 2 + } +] + +### 05 cosmosdb ### +cosmosdb_name = "demogptcosmosdb" +cosmosdb_offer_type = "Standard" +cosmosdb_kind = "MongoDB" +cosmosdb_automatic_failover = false +use_cosmosdb_free_tier = true +cosmosdb_consistency_level = "BoundedStaleness" +cosmosdb_max_interval_in_seconds = 10 +cosmosdb_max_staleness_prefix = 200 +cosmosdb_geo_locations = [ + { + location = "SwedenCentral" + failover_priority = 0 + } +] +cosmosdb_capabilities = ["EnableMongo", "MongoDBv3.4"] +cosmosdb_virtual_network_subnets = null +cosmosdb_is_virtual_network_filter_enabled = true +cosmosdb_public_network_access_enabled = true + +### 06 app services (librechat app + meilisearch) ### +# App Service Plan +app_service_name = "demogptasp" +app_service_sku_name = "B1" + +# LibreChat App Service +libre_app_name = "demogptchatapp" +libre_app_public_network_access_enabled = true +libre_app_virtual_network_subnet_id = null # Access is allowed on the built in subnet of this module. If networking is created as part of the module, this will be automatically populated if value is 'null' (priority 100) +libre_app_allowed_subnets = null # Add any other subnet ids to allow access to the app service (optional) +libre_app_allowed_ip_addresses = [ + { + ip_address = "81.103.78.62/32" + priority = 200 + name = "ip-access-rule1" + action = "Allow" + } +] + +### LibreChat App Settings ### +# Server Config +libre_app_title = "MONKEY GPT" +libre_app_custom_footer = "Privately hosted Monkey GPT App powered by Azure OpenAI and LibreChat" +libre_app_host = "0.0.0.0" +libre_app_port = 80 +libre_app_docker_image = "ghcr.io/danny-avila/librechat-dev-api:81ff598eba338e680c91e237cea3e3df870bce23" #v0.6.6 (Pre-release) +libre_app_mongo_uri = null +libre_app_domain_client = "http://localhost:80" +libre_app_domain_server = "http://localhost:80" + +# debug logging +libre_app_debug_logging = true +libre_app_debug_console = false + +# Endpoints +libre_app_endpoints = "azureOpenAI" + +# Azure OpenAI +libre_app_az_oai_api_key = null +libre_app_az_oai_models = "gpt-35-turbo,gpt-4,gpt-4-vision-preview" +libre_app_az_oai_use_model_as_deployment_name = true +libre_app_az_oai_instance_name = null +libre_app_az_oai_api_version = "2023-07-01-preview" +libre_app_az_oai_dall3_api_version = "2023-12-01-preview" +libre_app_az_oai_dall3_deployment_name = "dall-e-3" + +# Plugins +libre_app_debug_plugins = true +libre_app_plugins_creds_key = null +libre_app_plugins_creds_iv = null + +# Search +libre_app_enable_meilisearch = false + +# User Registration +libre_app_allow_email_login = true +libre_app_allow_registration = true +libre_app_allow_social_login = false +libre_app_allow_social_registration = false +libre_app_jwt_secret = null +libre_app_jwt_refresh_secret = null + +# violations +libre_app_violations = { + enabled = false + ban_duration = 1000 * 60 * 60 * 2 + ban_interval = 20 + login_violation_score = 1 + registration_violation_score = 1 + concurrent_violation_score = 1 + message_violation_score = 1 + non_browser_violation_score = 20 + login_max = 7 + login_window = 5 + register_max = 5 + register_window = 60 + limit_concurrent_messages = false + concurrent_message_max = 2 + limit_message_ip = false + message_ip_max = 40 + message_ip_window = 1 + limit_message_user = false + message_user_max = 40 + message_user_window = 1 +} + +# Custom Domain and Managed Certificate (Optional) +libre_app_custom_domain_create = true +librechat_app_custom_domain_name = "demogpt" +librechat_app_custom_dns_zone_name = "pwd9000.com" +dns_resource_group_name = "Pwd9000-EB-Network" diff --git a/examples/public_deployment_with_custom_domain/main.tf b/examples/public_deployment_with_custom_domain/main.tf new file mode 100644 index 0000000..31d63b1 --- /dev/null +++ b/examples/public_deployment_with_custom_domain/main.tf @@ -0,0 +1,144 @@ +terraform { + #backend "azurerm" {} + backend "local" { path = "terraform-example1.tfstate" } +} + +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = true + } + } +} + +# ################################################# +# # PRE-REQS # +# ################################################# +### Random integer to generate unique names +resource "random_integer" "number" { + min = 0001 + max = 9999 +} + +module "private-chatgpt-openai" { + source = "Pwd9000-ML/openai-private-chatgpt/azurerm" + version = "~> 2.2.0" + + # 01 common + RG # + #================# + location = var.location + tags = var.tags + resource_group_name = var.resource_group_name + + # 02 networking # + #===============# + virtual_network_name = "${var.virtual_network_name}${random_integer.number.result}" + vnet_address_space = var.vnet_address_space + subnet_config = var.subnet_config + + # 03 keyvault (Solution Secrets) + #==============================# + kv_name = "${var.kv_name}${random_integer.number.result}" + kv_sku = var.kv_sku + kv_fw_default_action = var.kv_fw_default_action + kv_fw_bypass = var.kv_fw_bypass + kv_fw_allowed_ips = var.kv_fw_allowed_ips + kv_fw_network_subnet_ids = var.kv_fw_network_subnet_ids + + # 04 openai service + #==================# + oai_account_name = "${var.oai_account_name}${random_integer.number.result}" + oai_sku_name = var.oai_sku_name + oai_custom_subdomain_name = "${var.oai_custom_subdomain_name}${random_integer.number.result}" + oai_dynamic_throttling_enabled = var.oai_dynamic_throttling_enabled + oai_fqdns = var.oai_fqdns + oai_local_auth_enabled = var.oai_local_auth_enabled + oai_outbound_network_access_restricted = var.oai_outbound_network_access_restricted + oai_public_network_access_enabled = var.oai_public_network_access_enabled + oai_customer_managed_key = var.oai_customer_managed_key + oai_identity = var.oai_identity + oai_network_acls = var.oai_network_acls + oai_storage = var.oai_storage + oai_model_deployment = var.oai_model_deployment + + # 05 cosmosdb + #============# + cosmosdb_name = "${var.cosmosdb_name}${random_integer.number.result}" + cosmosdb_offer_type = var.cosmosdb_offer_type + cosmosdb_kind = var.cosmosdb_kind + cosmosdb_automatic_failover = var.cosmosdb_automatic_failover + use_cosmosdb_free_tier = var.use_cosmosdb_free_tier + cosmosdb_consistency_level = var.cosmosdb_consistency_level + cosmosdb_max_interval_in_seconds = var.cosmosdb_max_interval_in_seconds + cosmosdb_max_staleness_prefix = var.cosmosdb_max_staleness_prefix + cosmosdb_geo_locations = var.cosmosdb_geo_locations + cosmosdb_capabilities = var.cosmosdb_capabilities + cosmosdb_virtual_network_subnets = var.cosmosdb_virtual_network_subnets + cosmosdb_is_virtual_network_filter_enabled = var.cosmosdb_is_virtual_network_filter_enabled + cosmosdb_public_network_access_enabled = var.cosmosdb_public_network_access_enabled + + # 06 app services (librechat app + meilisearch) + #=============================================# + # App Service Plan + app_service_name = "${var.app_service_name}${random_integer.number.result}" + app_service_sku_name = var.app_service_sku_name + + # LibreChat App + libre_app_name = "${var.libre_app_name}${random_integer.number.result}" + libre_app_virtual_network_subnet_id = var.libre_app_virtual_network_subnet_id + libre_app_public_network_access_enabled = var.libre_app_public_network_access_enabled + libre_app_allowed_subnets = var.libre_app_allowed_subnets + libre_app_allowed_ip_addresses = var.libre_app_allowed_ip_addresses + + ### LibreChat App Settings ### + # Server Config + libre_app_title = var.libre_app_title + libre_app_custom_footer = var.libre_app_custom_footer + libre_app_host = var.libre_app_host + libre_app_port = var.libre_app_port + libre_app_docker_image = var.libre_app_docker_image + libre_app_mongo_uri = var.libre_app_mongo_uri + libre_app_domain_client = var.libre_app_domain_client + libre_app_domain_server = var.libre_app_domain_server + + # Debug Config + libre_app_debug_logging = var.libre_app_debug_logging + libre_app_debug_console = var.libre_app_debug_console + + # Endpoints + libre_app_endpoints = var.libre_app_endpoints + + # Azure OpenAI Config + libre_app_az_oai_api_key = var.libre_app_az_oai_api_key + libre_app_az_oai_models = var.libre_app_az_oai_models + libre_app_az_oai_use_model_as_deployment_name = var.libre_app_az_oai_use_model_as_deployment_name + libre_app_az_oai_instance_name = var.libre_app_az_oai_instance_name + libre_app_az_oai_api_version = var.libre_app_az_oai_api_version + libre_app_az_oai_dall3_api_version = var.libre_app_az_oai_dall3_api_version + libre_app_az_oai_dall3_deployment_name = var.libre_app_az_oai_dall3_deployment_name + + # Plugins + libre_app_debug_plugins = var.libre_app_debug_plugins + libre_app_plugins_creds_key = var.libre_app_plugins_creds_key + libre_app_plugins_creds_iv = var.libre_app_plugins_creds_iv + + # Search + libre_app_enable_meilisearch = var.libre_app_enable_meilisearch + + # User Registration + libre_app_allow_email_login = var.libre_app_allow_email_login + libre_app_allow_registration = var.libre_app_allow_registration + libre_app_allow_social_login = var.libre_app_allow_social_login + libre_app_allow_social_registration = var.libre_app_allow_social_registration + libre_app_jwt_secret = var.libre_app_jwt_secret + libre_app_jwt_refresh_secret = var.libre_app_jwt_refresh_secret + + # Violations + libre_app_violations = var.libre_app_violations + + # Custom Domain and Managed Certificate (Optional) + libre_app_custom_domain_create = var.libre_app_custom_domain_create + librechat_app_custom_domain_name = var.librechat_app_custom_domain_name + librechat_app_custom_dns_zone_name = var.librechat_app_custom_dns_zone_name + dns_resource_group_name = var.dns_resource_group_name +} \ No newline at end of file diff --git a/examples/public_deployment_with_custom_domain/variables.tf b/examples/public_deployment_with_custom_domain/variables.tf new file mode 100644 index 0000000..87b9fc2 --- /dev/null +++ b/examples/public_deployment_with_custom_domain/variables.tf @@ -0,0 +1,669 @@ +### 01 common + RG ### +variable "location" { + type = string + default = "uksouth" + description = "Azure region where resources will be hosted." +} + +variable "tags" { + type = map(string) + default = {} + description = "A map of key value pairs that is used to tag resources created." +} + +variable "resource_group_name" { + type = string + description = "Name of the resource group to create where the cognitive account OpenAI service is hosted." + nullable = false +} + +### 02 networking ### +variable "virtual_network_name" { + type = string + default = "openai-vnet-9000" + description = "Name of the virtual network where resources are attached." +} + +variable "vnet_address_space" { + type = list(string) + default = ["10.4.0.0/24"] + description = "value of the address space for the virtual network." +} + +variable "subnet_config" { + type = object({ + subnet_name = string + subnet_address_space = list(string) + service_endpoints = list(string) + private_endpoint_network_policies_enabled = bool + private_link_service_network_policies_enabled = bool + subnets_delegation_settings = map(list(object({ + name = string + actions = list(string) + }))) + }) + default = { + subnet_name = "app-cosmos-sub" + subnet_address_space = ["10.4.0.0/24"] + service_endpoints = ["Microsoft.AzureCosmosDB", "Microsoft.Web"] + private_endpoint_network_policies_enabled = false + private_link_service_network_policies_enabled = false + subnets_delegation_settings = { + app-service-plan = [ + { + name = "Microsoft.Web/serverFarms" + actions = ["Microsoft.Network/virtualNetworks/subnets/action"] + } + ] + } + } + description = "A list of subnet configuration objects to create subnets in the virtual network." +} + +### 03 key vault ### +variable "kv_name" { + type = string + description = "Name of the Key Vault to create (solution secrets)." + default = "openaikv9000" +} + +variable "kv_sku" { + type = string + description = "SKU of the Key Vault to create." + default = "standard" +} + +variable "kv_fw_default_action" { + type = string + default = "Deny" + description = "Default action for key vault firewall rules." + +} + +variable "kv_fw_bypass" { + type = string + default = "AzureServices" + description = "List of key vault firewall rules to bypass." +} + +variable "kv_fw_allowed_ips" { + type = list(string) + default = [] + description = "value of key vault firewall allowed ip rules." +} + +variable "kv_fw_network_subnet_ids" { + description = "The virtual network subnets to associate with the Cosmos DB account (Service Endpoint). If networking is created as part of the module, this will be automatically populated." + type = list(string) + default = null +} + +### 04 openai service ### +variable "oai_account_name" { + type = string + default = "az-openai-account" + description = "The name of the OpenAI service." +} + +variable "oai_sku_name" { + type = string + description = "SKU name of the OpenAI service." + default = "S0" +} + +variable "oai_custom_subdomain_name" { + type = string + description = "The subdomain name used for token-based authentication. Changing this forces a new resource to be created. (normally the same as the account name)" + default = "demo-account" +} + +variable "oai_dynamic_throttling_enabled" { + type = bool + default = true + description = "Whether or not dynamic throttling is enabled. Defaults to `true`." +} + +variable "oai_fqdns" { + type = list(string) + default = [] + description = "A list of FQDNs to be used for token-based authentication. Changing this forces a new resource to be created." +} + +variable "oai_local_auth_enabled" { + type = bool + default = true + description = "Whether local authentication methods is enabled for the Cognitive Account. Defaults to `true`." +} + +variable "oai_outbound_network_access_restricted" { + type = bool + default = false + description = "Whether or not outbound network access is restricted. Defaults to `false`." +} + +variable "oai_public_network_access_enabled" { + type = bool + default = false + description = "Whether or not public network access is enabled. Defaults to `false`." +} + +variable "oai_customer_managed_key" { + type = object({ + key_vault_key_id = string + identity_client_id = optional(string) + }) + default = null + description = <<-DESCRIPTION + type = object({ + key_vault_key_id = (Required) The ID of the Key Vault Key which should be used to Encrypt the data in this OpenAI Account. + identity_client_id = (Optional) The Client ID of the User Assigned Identity that has access to the key. This property only needs to be specified when there're multiple identities attached to the OpenAI Account. + }) + DESCRIPTION +} + +variable "oai_identity" { + type = object({ + type = string + identity_ids = optional(list(string)) + }) + default = { + type = "SystemAssigned" + } + description = <<-DESCRIPTION + type = object({ + type = (Required) The type of the Identity. Possible values are `SystemAssigned`, `UserAssigned`, `SystemAssigned, UserAssigned`. + identity_ids = (Optional) Specifies a list of User Assigned Managed Identity IDs to be assigned to this OpenAI Account. + }) + DESCRIPTION +} + +variable "oai_network_acls" { + type = set(object({ + default_action = string + ip_rules = optional(set(string)) + virtual_network_rules = optional(set(object({ + subnet_id = string + ignore_missing_vnet_service_endpoint = optional(bool, false) + }))) + })) + default = null + description = <<-DESCRIPTION + type = set(object({ + default_action = (Required) The Default Action to use when no rules match from ip_rules / virtual_network_rules. Possible values are `Allow` and `Deny`. + ip_rules = (Optional) One or more IP Addresses, or CIDR Blocks which should be able to access the Cognitive Account. + virtual_network_rules = optional(set(object({ + subnet_id = (Required) The ID of a Subnet which should be able to access the OpenAI Account. + ignore_missing_vnet_service_endpoint = (Optional) Whether ignore missing vnet service endpoint or not. Default to `false`. + }))) + })) + DESCRIPTION +} + +variable "oai_storage" { + type = list(object({ + storage_account_id = string + identity_client_id = optional(string) + })) + default = [] + description = <<-DESCRIPTION + type = list(object({ + storage_account_id = (Required) Full resource id of a Microsoft.Storage resource. + identity_client_id = (Optional) The client ID of the managed identity associated with the storage resource. + })) + DESCRIPTION + nullable = false +} + +variable "oai_model_deployment" { + type = list(object({ + deployment_id = string + model_name = string + model_format = string + model_version = string + scale_type = string + scale_tier = optional(string) + scale_size = optional(number) + scale_family = optional(string) + scale_capacity = optional(number) + rai_policy_name = optional(string) + })) + default = [] + description = <<-DESCRIPTION + type = list(object({ + deployment_id = (Required) The name of the Cognitive Services Account `Model Deployment`. Changing this forces a new resource to be created. + model_name = { + model_format = (Required) The format of the Cognitive Services Account Deployment model. Changing this forces a new resource to be created. Possible value is OpenAI. + model_name = (Required) The name of the Cognitive Services Account Deployment model. Changing this forces a new resource to be created. + model_version = (Required) The version of Cognitive Services Account Deployment model. + } + scale = { + scale_type = (Required) Deployment scale type. Possible value is Standard. Changing this forces a new resource to be created. + scale_tier = (Optional) Possible values are Free, Basic, Standard, Premium, Enterprise. Changing this forces a new resource to be created. + scale_size = (Optional) The SKU size. When the name field is the combination of tier and some other value, this would be the standalone code. Changing this forces a new resource to be created. + scale_family = (Optional) If the service has different generations of hardware, for the same SKU, then that can be captured here. Changing this forces a new resource to be created. + scale_capacity = (Optional) Tokens-per-Minute (TPM). If the SKU supports scale out/in then the capacity integer should be included. If scale out/in is not possible for the resource this may be omitted. Default value is 1. Changing this forces a new resource to be created. + } + rai_policy_name = (Optional) The name of RAI policy. Changing this forces a new resource to be created. + })) + DESCRIPTION + nullable = false +} + +### 05 OpenAI CosmosDB ### +variable "cosmosdb_name" { + description = "The name of the Cosmos DB account" + type = string + default = "openaicosmosdb" +} + +variable "cosmosdb_offer_type" { + description = "The offer type to use for the Cosmos DB account" + type = string + default = "Standard" +} + +variable "cosmosdb_kind" { + description = "The kind of Cosmos DB to create" + type = string + default = "MongoDB" +} + +variable "cosmosdb_automatic_failover" { + description = "Whether to enable automatic failover for the Cosmos DB account" + type = bool + default = false +} + +variable "use_cosmosdb_free_tier" { + description = "Whether to enable the free tier for the Cosmos DB account. This needs to be false if another instance already uses free tier." + type = bool + default = true +} + +variable "cosmosdb_consistency_level" { + description = "The consistency level of the Cosmos DB account" + type = string + default = "BoundedStaleness" +} + +variable "cosmosdb_max_interval_in_seconds" { + description = "The maximum staleness interval in seconds for the Cosmos DB account" + type = number + default = 10 +} + +variable "cosmosdb_max_staleness_prefix" { + description = "The maximum staleness prefix for the Cosmos DB account" + type = number + default = 200 +} + +variable "cosmosdb_geo_locations" { + description = "The geo-locations for the Cosmos DB account" + type = list(object({ + location = string + failover_priority = number + })) + default = [ + { + location = "uksouth" + failover_priority = 0 + } + ] +} + +variable "cosmosdb_capabilities" { + description = "The capabilities for the Cosmos DB account" + type = list(string) + default = ["EnableMongo", "MongoDBv3.4"] +} + +variable "cosmosdb_virtual_network_subnets" { + description = "The virtual network subnets to associate with the Cosmos DB account (Service Endpoint). If networking is created as part of the module, this will be automatically populated." + type = list(string) + default = null +} + +variable "cosmosdb_is_virtual_network_filter_enabled" { + description = "Whether to enable virtual network filtering for the Cosmos DB account" + type = bool + default = true +} + +variable "cosmosdb_public_network_access_enabled" { + description = "Whether to enable public network access for the Cosmos DB account" + type = bool + default = true +} + +### 06 app services (librechat app + meilisearch) ### +# App service Plan +variable "app_service_name" { + type = string + description = "Name of the Linux App Service Plan." + default = "openai-asp9000" +} + +variable "app_service_sku_name" { + type = string + description = "The SKU name of the App Service Plan." + default = "B1" +} + +# LibreChat App Service +variable "libre_app_name" { + type = string + description = "Name of the LibreChat App Service." + default = "librechatapp9000" +} + +variable "libre_app_public_network_access_enabled" { + type = bool + description = "Whether or not public network access is enabled. Defaults to `false`." + default = true +} + +variable "libre_app_virtual_network_subnet_id" { + type = string + description = "The ID of the subnet, used to allow access to the App Service (priority 100), e.g. cosmosdb, meilisearch etc. If networking is created as part of the module, this will be automatically populated if value is 'null'." + default = null +} + +variable "libre_app_allowed_subnets" { + description = "Allowed Subnets (By default the subnet the app service is deployed in is allowed access already as priority 100). Add any additionals here" + type = list(object({ + virtual_network_subnet_id = string + priority = number + name = string + action = string + })) + default = [ + { + virtual_network_subnet_id = "subnet_id1" + priority = 200 + name = "subnet-access-rule1" + action = "Allow" + } + ] +} + +variable "libre_app_allowed_ip_addresses" { + description = "Allowed IP Addresses. The CIDR notation of the IP or IP Range to match to allow. For example: 10.0.0.0/24 or 192.168.10.1/32" + type = list(object({ + ip_address = string + priority = number + name = string + action = string + })) + default = [ + { + ip_address = "0.0.0.0/0" # Allow all IP Addresses (change to your IP range) + priority = 300 + name = "ip-access-rule1" + action = "Allow" + } + ] +} + +# LibreChat App Service App Settings +# Server Config +variable "libre_app_title" { + type = string + description = "Add a custom title for the App." + default = "PrivateGPT" +} + +variable "libre_app_custom_footer" { + type = string + description = "Add a custom footer for the App." + default = "Privately hosted chat app powered by Azure OpenAI and LibreChat." +} + +variable "libre_app_host" { + type = string + description = "he server will listen to localhost:3080 by default. You can change the target IP as you want. If you want to make this server available externally, for example to share the server with others or expose this from a Docker container, set host to 0.0.0.0 or your external IP interface." + default = "0.0.0.0" +} + +variable "libre_app_port" { + type = number + description = "The host port to listen on." + default = 3080 +} + +variable "libre_app_docker_image" { + type = string + description = "The Docker Image to use for the App Service." + default = "ghcr.io/danny-avila/librechat-dev-api:latest" +} + +variable "libre_app_mongo_uri" { + type = string + description = "The MongoDB Connection String to connect to." + default = null + sensitive = true +} + +variable "libre_app_domain_client" { + type = string + description = "To use locally, set DOMAIN_CLIENT and DOMAIN_SERVER to http://localhost:3080 (3080 being the port previously configured).When deploying to a custom domain, set DOMAIN_CLIENT and DOMAIN_SERVER to your deployed URL, e.g. https://mydomain.example.com" + default = "http://localhost:3080" +} + +variable "libre_app_domain_server" { + type = string + description = "To use locally, set DOMAIN_CLIENT and DOMAIN_SERVER to http://localhost:3080 (3080 being the port previously configured).When deploying to a custom domain, set DOMAIN_CLIENT and DOMAIN_SERVER to your deployed URL, e.g. https://mydomain.example.com" + default = "http://localhost:3080" +} + +# Debug logging +variable "libre_app_debug_logging" { + type = bool + description = "LibreChat has central logging built into its backend (api). Log files are saved in /api/logs. Error logs are saved by default. Debug logs are enabled by default but can be turned off if not desired." + default = false +} + +variable "libre_app_debug_console" { + type = bool + description = "Enable verbose server output in the console, though it's not recommended due to high verbosity." + default = false +} + +# Endpoints +variable "libre_app_endpoints" { + type = string + description = "endpoints and models selection. E.g. 'openAI,azureOpenAI,bingAI,chatGPTBrowser,google,gptPlugins,anthropic'" + default = "azureOpenAI" +} + +# Azure OpenAI +variable "libre_app_az_oai_api_key" { + type = string + description = "Azure OpenAI API Key" + default = null + sensitive = true +} + +variable "libre_app_az_oai_models" { + type = string + description = "Azure OpenAI Models. E.g. 'gpt-4-1106-preview,gpt-4,gpt-3.5-turbo,gpt-3.5-turbo-1106,gpt-4-vision-preview'" + default = "gpt-4-1106-preview" +} + +variable "libre_app_az_oai_use_model_as_deployment_name" { + type = bool + description = "Azure OpenAI Use Model as Deployment Name" + default = true +} + +variable "libre_app_az_oai_instance_name" { + type = string + description = "Azure OpenAI Instance Name" + default = null +} + +variable "libre_app_az_oai_api_version" { + type = string + description = "Azure OpenAI API Version" + default = "2023-07-01-preview" +} + +variable "libre_app_az_oai_dall3_api_version" { + type = string + description = "Azure OpenAI DALL-E API Version" + default = "2023-12-01-preview" +} + +variable "libre_app_az_oai_dall3_deployment_name" { + type = string + description = "Azure OpenAI DALL-E Deployment Name" + default = "dall-e-3" +} + +# Plugins +variable "libre_app_debug_plugins" { + type = bool + description = "Enable debug mode for Libre App plugins." + default = false +} + +variable "libre_app_plugins_creds_key" { + type = string + description = "Libre App Plugins Creds Key" + default = null + sensitive = true +} + +variable "libre_app_plugins_creds_iv" { + type = string + description = "Libre App Plugins Creds IV" + default = null + sensitive = true +} + +# Search +variable "libre_app_enable_meilisearch" { + type = bool + description = "Enable Meilisearch" + default = false +} + +variable "libre_app_meili_key" { + type = string + description = "Meilisearch API Key" + default = null + sensitive = true +} + +# User Registration +variable "libre_app_allow_email_login" { + type = bool + description = "Allow Email Login" + default = true +} + +variable "libre_app_allow_registration" { + type = bool + description = "Allow Registration" + default = true +} + +variable "libre_app_allow_social_login" { + type = bool + description = "Allow Social Login" + default = false +} + +variable "libre_app_allow_social_registration" { + type = bool + description = "Allow Social Registration" + default = false +} + +variable "libre_app_jwt_secret" { + type = string + description = "JWT Secret" + default = null + sensitive = true +} + +variable "libre_app_jwt_refresh_secret" { + type = string + description = "JWT Refresh Secret" + default = null + sensitive = true +} + +# Violations +variable "libre_app_violations" { + description = "Configuration for violations" + type = object({ + enabled = bool + ban_duration = number + ban_interval = number + login_violation_score = number + registration_violation_score = number + concurrent_violation_score = number + message_violation_score = number + non_browser_violation_score = number + login_max = number + login_window = number + register_max = number + register_window = number + limit_concurrent_messages = bool + concurrent_message_max = number + limit_message_ip = bool + message_ip_max = number + message_ip_window = number + limit_message_user = bool + message_user_max = number + message_user_window = number + }) + default = { + enabled = true + ban_duration = 1000 * 60 * 60 * 2 + ban_interval = 20 + login_violation_score = 1 + registration_violation_score = 1 + concurrent_violation_score = 1 + message_violation_score = 1 + non_browser_violation_score = 20 + login_max = 7 + login_window = 5 + register_max = 5 + register_window = 60 + limit_concurrent_messages = true + concurrent_message_max = 2 + limit_message_ip = true + message_ip_max = 40 + message_ip_window = 1 + limit_message_user = false + message_user_max = 40 + message_user_window = 1 + } +} + +# Custom Domain and Managed Certificate (Optional) +variable "libre_app_custom_domain_create" { + type = bool + description = "Create a custom domain and managed certificate for the App Service." + default = false +} + +variable "librechat_app_custom_domain_name" { + type = string + description = "The custom domain to use for the App Service." + default = "privategpt" +} + +variable "librechat_app_custom_dns_zone_name" { + type = string + description = "The DNS Zone to use for the App Service." + default = "domain.com" +} + +variable "dns_resource_group_name" { + type = string + description = "The Resource Group that contains the custom DNS Zone to use for the App Service" + default = "dns-rg" +} \ No newline at end of file From b1801e1d1c63cd571d09f878d24f2a5bce55ef26 Mon Sep 17 00:00:00 2001 From: Pwd9000-ML Date: Wed, 24 Jan 2024 12:08:30 +0000 Subject: [PATCH 7/8] update module release v2.2.0 --- 03_keyvault.tf | 1 - README.md | 3 +- .../Coming_soon.md | 0 .../common.auto.tfvars | 34 +-- outputs.tf | 195 ++++++++++++------ 5 files changed, 154 insertions(+), 79 deletions(-) rename examples/{ => private_deployment_with_front_door}/Coming_soon.md (100%) diff --git a/03_keyvault.tf b/03_keyvault.tf index c7e7ca1..df84b60 100644 --- a/03_keyvault.tf +++ b/03_keyvault.tf @@ -17,7 +17,6 @@ resource "azurerm_key_vault" "az_openai_kv" { depends_on = [azurerm_subnet.az_openai_subnet] } - # Add "self" permission to key vault RBAC (to manange key vault secrets) resource "azurerm_role_assignment" "kv_role_assigment" { for_each = toset(["Key Vault Administrator"]) diff --git a/README.md b/README.md index f879f78..f61daae 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ New integrations and features have been added to the module to use the latest ** - [x] Added User Violations Support - [x] Updated documentation and examples +- [x] Added module outputs ### v2.1.x @@ -60,7 +61,7 @@ coming soon... ## Examples -coming soon... +- [Public Deployment with Custom Domain and IP Whitelisting](https://github.com/Pwd9000-ML/terraform-azurerm-openai-private-chatgpt/tree/master/examples/public_deployment_with_custom_domain) Enjoy! diff --git a/examples/Coming_soon.md b/examples/private_deployment_with_front_door/Coming_soon.md similarity index 100% rename from examples/Coming_soon.md rename to examples/private_deployment_with_front_door/Coming_soon.md diff --git a/examples/public_deployment_with_custom_domain/common.auto.tfvars b/examples/public_deployment_with_custom_domain/common.auto.tfvars index cd9621e..726caf1 100644 --- a/examples/public_deployment_with_custom_domain/common.auto.tfvars +++ b/examples/public_deployment_with_custom_domain/common.auto.tfvars @@ -1,5 +1,5 @@ ### 01 Common Variables + RG ### -resource_group_name = "Private-ChatGPT-OpenAI-Example" +resource_group_name = "Private-ChatGPT-OpenAI-LibreChat-Example" location = "SwedenCentral" tags = { Terraform = "True" @@ -32,8 +32,8 @@ kv_name = "demogptkv" kv_sku = "standard" kv_fw_default_action = "Deny" kv_fw_bypass = "AzureServices" -kv_fw_allowed_ips = ["0.0.0.0/0"] -kv_fw_network_subnet_ids = null +kv_fw_allowed_ips = ["0.0.0.0/0"] # Allow all IPs (for demo purposes) +kv_fw_network_subnet_ids = null # leave null to allow access from default subnet of this module ### 04 Create OpenAI Service ### oai_account_name = "demogptoai" @@ -101,7 +101,7 @@ cosmosdb_geo_locations = [ } ] cosmosdb_capabilities = ["EnableMongo", "MongoDBv3.4"] -cosmosdb_virtual_network_subnets = null +cosmosdb_virtual_network_subnets = null # leave null to allow access from default subnet of this module cosmosdb_is_virtual_network_filter_enabled = true cosmosdb_public_network_access_enabled = true @@ -117,7 +117,7 @@ libre_app_virtual_network_subnet_id = null # Access is allowed on the built libre_app_allowed_subnets = null # Add any other subnet ids to allow access to the app service (optional) libre_app_allowed_ip_addresses = [ { - ip_address = "81.103.78.62/32" + ip_address = "0.0.0.0/0" # Allow all IPs (for demo purposes) priority = 200 name = "ip-access-rule1" action = "Allow" @@ -126,12 +126,12 @@ libre_app_allowed_ip_addresses = [ ### LibreChat App Settings ### # Server Config -libre_app_title = "MONKEY GPT" -libre_app_custom_footer = "Privately hosted Monkey GPT App powered by Azure OpenAI and LibreChat" +libre_app_title = "PRIVATE DEMO CHATBOT" +libre_app_custom_footer = "Privately hosted GPT App powered by Azure OpenAI and LibreChat" libre_app_host = "0.0.0.0" libre_app_port = 80 libre_app_docker_image = "ghcr.io/danny-avila/librechat-dev-api:81ff598eba338e680c91e237cea3e3df870bce23" #v0.6.6 (Pre-release) -libre_app_mongo_uri = null +libre_app_mongo_uri = null # leave null to use the cosmosdb uri saved in keyvault created by this module libre_app_domain_client = "http://localhost:80" libre_app_domain_server = "http://localhost:80" @@ -143,18 +143,18 @@ libre_app_debug_console = false libre_app_endpoints = "azureOpenAI" # Azure OpenAI -libre_app_az_oai_api_key = null +libre_app_az_oai_api_key = null # leave null to use the key saved in keyvault created by this module libre_app_az_oai_models = "gpt-35-turbo,gpt-4,gpt-4-vision-preview" libre_app_az_oai_use_model_as_deployment_name = true -libre_app_az_oai_instance_name = null +libre_app_az_oai_instance_name = null # leave null to use the instance name created by this module libre_app_az_oai_api_version = "2023-07-01-preview" libre_app_az_oai_dall3_api_version = "2023-12-01-preview" libre_app_az_oai_dall3_deployment_name = "dall-e-3" # Plugins libre_app_debug_plugins = true -libre_app_plugins_creds_key = null -libre_app_plugins_creds_iv = null +libre_app_plugins_creds_key = null # leave null to use the key saved in keyvault created by this module +libre_app_plugins_creds_iv = null # leave null to use the iv saved in keyvault created by this module # Search libre_app_enable_meilisearch = false @@ -164,8 +164,8 @@ libre_app_allow_email_login = true libre_app_allow_registration = true libre_app_allow_social_login = false libre_app_allow_social_registration = false -libre_app_jwt_secret = null -libre_app_jwt_refresh_secret = null +libre_app_jwt_secret = null # leave null to use the secret saved in keyvault created by this module +libre_app_jwt_refresh_secret = null # leave null to use the refresh secret saved in keyvault created by this module # violations libre_app_violations = { @@ -193,6 +193,6 @@ libre_app_violations = { # Custom Domain and Managed Certificate (Optional) libre_app_custom_domain_create = true -librechat_app_custom_domain_name = "demogpt" -librechat_app_custom_dns_zone_name = "pwd9000.com" -dns_resource_group_name = "Pwd9000-EB-Network" +librechat_app_custom_domain_name = "privategptchatbot" +librechat_app_custom_dns_zone_name = "domain.com" +dns_resource_group_name = "DNS-Resource-Group-Name" diff --git a/outputs.tf b/outputs.tf index 3b530d4..dd69d42 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,63 +1,138 @@ # ################################################# # # OUTPUTS # # ################################################# -# ## OpenAI Service Account Details -# output "openai_endpoint" { -# description = "The endpoint used to connect to the Cognitive Service Account." -# value = module.openai.openai_endpoint -# } - -# output "openai_primary_key" { -# description = "The primary access key for the Cognitive Service Account." -# sensitive = true -# value = module.openai.openai_primary_key -# } - -# output "openai_secondary_key" { -# description = "The secondary access key for the Cognitive Service Account." -# sensitive = true -# value = module.openai.openai_secondary_key -# } - -# output "openai_subdomain" { -# description = "The subdomain used to connect to the Cognitive Service Account." -# value = module.openai.openai_subdomain -# } - -# output "openai_account_name" { -# description = "The name of the Cognitive Service Account." -# value = module.openai.openai_account_name -# } - -# ## key vault -# output "key_vault_id" { -# description = "The ID of the Key Vault used to store OpenAI account and model details." -# value = module.openai.key_vault_id -# } - -# output "key_vault_uri" { -# description = "The URI of the Key Vault used to store OpenAI account and model details.." -# value = module.openai.key_vault_uri -# } - -# ## Container App Enviornment -# #output "container_app_enviornment_id" { -# # description = "The ID of the container app enviornment." -# # value = module.privategpt_chatbot_container_apps.container_app_environment_id -# #} - -# ## Container App -# #output "container_app_id" { -# # description = "The ID of the container app." -# # value = module.privategpt_chatbot_container_apps.container_app_id -# #} - -# #output "latest_revision_fqdn" { -# # description = "The FQDN of the Latest Revision of the Container App." -# # value = module.privategpt_chatbot_container_apps.latest_revision_fqdn -# #} - -# #output "latest_revision_name" { -# # description = "The Name of the Latest Revision of the Container App." -# # value = module.privategpt_chatbot_container_apps.latest_revision_name -# #} + +# Network Details +output "virtual_network_id" { + description = "The ID of the virtual network" + value = azurerm_virtual_network.az_openai_vnet.id +} + +output "virtual_network_name" { + description = "The name of the virtual network" + value = azurerm_virtual_network.az_openai_vnet.name +} + +output "subnet_id" { + description = "The ID of the subnet" + value = azurerm_subnet.az_openai_subnet.id +} + +output "subnet_name" { + description = "The name of the subnet" + value = azurerm_subnet.az_openai_subnet.name +} + +# Key Vault Details +output "key_vault_id" { + description = "The ID of the Key Vault" + value = azurerm_key_vault.az_openai_kv.id +} + +output "key_vault_name" { + description = "The name of the Key Vault" + value = azurerm_key_vault.az_openai_kv.name +} + +output "key_vault_uri" { + description = "The URI of the Key Vault" + value = azurerm_key_vault.az_openai_kv.uri +} + +# OPENAI Details +output "openai_endpoint" { + description = "The endpoint used to connect to the Cognitive Service Account." + value = azurerm_cognitive_account.az_openai.endpoint +} + +output "openai_primary_key" { + description = "The primary access key for the Cognitive Service Account." + sensitive = true + value = azurerm_cognitive_account.az_openai.primary_access_key +} + +output "openai_secondary_key" { + description = "The secondary access key for the Cognitive Service Account." + sensitive = true + value = azurerm_cognitive_account.az_openai.secondary_access_key +} + +output "openai_subdomain" { + description = "The subdomain used to connect to the Cognitive Service Account." + value = azurerm_cognitive_account.az_openai.custom_subdomain_name +} + +output "cognitive_deployment_ids" { + description = "The IDs of the OpenAI Cognitive Account Model Deployments" + value = { for key, deployment in azurerm_cognitive_deployment.az_openai_models : key => deployment.id } +} + +output "cognitive_deployment_names" { + description = "The names of the OpenAI Cognitive Account Model Deployments" + value = { for key, deployment in azurerm_cognitive_deployment.az_openai_models : key => deployment.name } +} + +# CosmosDB Details +output "cosmosdb_account_id" { + description = "The ID of the Cosmos DB account" + value = azurerm_cosmosdb_account.az_openai_mongodb.id +} + +output "cosmosdb_account_name" { + description = "The name of the Cosmos DB account" + value = azurerm_cosmosdb_account.az_openai_mongodb.name +} + +output "cosmosdb_account_endpoint" { + description = "The endpoint used to connect to the Cosmos DB account" + value = azurerm_cosmosdb_account.az_openai_mongodb.endpoint +} + +output "cosmosdb_account_primary_key" { + description = "The primary master key for the Cosmos DB account" + sensitive = true + value = azurerm_cosmosdb_account.az_openai_mongodb.primary_key +} + +output "cosmosdb_account_secondary_key" { + description = "The secondary master key for the Cosmos DB account" + sensitive = true + value = azurerm_cosmosdb_account.az_openai_mongodb.secondary_key +} + +output "cosmosdb_account_connection_strings" { + description = "The primary connection string for the Cosmos DB account" + sensitive = true + value = azurerm_cosmosdb_account.az_openai_mongodb.connection_strings +} + +# App Service Details +output "app_service_plan_id" { + description = "The ID of the App Service Plan" + value = azurerm_service_plan.az_openai_asp.id +} + +output "app_service_plan_name" { + description = "The name of the App Service Plan" + value = azurerm_service_plan.az_openai_asp.name +} + +output "app_service_id" { + description = "The ID of the App Service" + value = azurerm_linux_web_app.librechat.id +} + +output "app_service_name" { + description = "The name of the App Service" + value = azurerm_linux_web_app.librechat.name +} + +output "app_service_default_hostname" { + description = "The default hostname of the App Service" + value = azurerm_linux_web_app.librechat.default_hostname +} + +output "app_service_outbound_ip_addresses" { + description = "The outbound IP addresses of the App Service" + value = azurerm_linux_web_app.librechat.outbound_ip_addresses +} From 0764026a826ad6171b8fcb033af1c397e7ddc72f Mon Sep 17 00:00:00 2001 From: Pwd9000-ML Date: Wed, 24 Jan 2024 12:25:52 +0000 Subject: [PATCH 8/8] update --- README.md | 16 +++++++++++++++- .../README.md | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f61daae..f87fbf6 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,21 @@ coming soon... - [Public Deployment with Custom Domain and IP Whitelisting](https://github.com/Pwd9000-ML/terraform-azurerm-openai-private-chatgpt/tree/master/examples/public_deployment_with_custom_domain) -Enjoy! +## Contributing + +Contributions are welcome. Please submit a pull request if you have any improvements or fixes. Make sure to follow the existing code style and add comments to your code explaining what it does. + +## License + +This terraform module is licensed under the MIT License. See the LICENSE file for more details. + +## Support + +If you encounter any issues or have any questions about this terraform module, please open an issue on GitHub. We'll do our best to respond as quickly as possible. + +## Acknowledgements + +This terraform module was developed by **Marcel Lupo** as part of a project to explore the capabilities of Azure OpenAI models. We'd like to thank the OpenAI and Microsoft team for their incredible work and ongoing support of the AI community. ## Requirements diff --git a/examples/public_deployment_with_custom_domain/README.md b/examples/public_deployment_with_custom_domain/README.md index de3a2a4..64e5600 100644 --- a/examples/public_deployment_with_custom_domain/README.md +++ b/examples/public_deployment_with_custom_domain/README.md @@ -1,4 +1,4 @@ -# Example - Public Deployment with Custom Domain and IP Whitelisting +# Public Deployment with Custom Domain and IP Whitelisting This example contains a Terraform script for provisioning a set of Azure resources for a chat application powered by Azure OpenAI models. The script is designed to be modular and configurable, allowing you to customise the resources and settings to fit your specific needs.