From 773c2d95da110294377ad170637a5194780a4424 Mon Sep 17 00:00:00 2001 From: Sal Scotto Date: Thu, 24 Oct 2024 12:50:34 -0600 Subject: [PATCH 01/11] Update configuration.rb Add a government flag option, for speech service hosted in a federal azure datacenter --- lib/azure_stt/configuration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/azure_stt/configuration.rb b/lib/azure_stt/configuration.rb index 2b04856..b576887 100644 --- a/lib/azure_stt/configuration.rb +++ b/lib/azure_stt/configuration.rb @@ -6,7 +6,7 @@ module AzureSTT # the key is in a .env file # class Configuration - attr_accessor :subscription_key, :region + attr_accessor :subscription_key, :region, :government end # From cb3abe6275f0d329dc5ef928dbbf00be82db671f Mon Sep 17 00:00:00 2001 From: Sal Scotto Date: Thu, 24 Oct 2024 12:52:21 -0600 Subject: [PATCH 02/11] Update session.rb Added governemtn flag passing --- lib/azure_stt/session.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/azure_stt/session.rb b/lib/azure_stt/session.rb index b53346d..f443872 100644 --- a/lib/azure_stt/session.rb +++ b/lib/azure_stt/session.rb @@ -18,8 +18,9 @@ class Session # read from configuration # def initialize(region: AzureSTT.configuration.region, - subscription_key: AzureSTT.configuration.subscription_key) - @client = Client.new(region: region, subscription_key: subscription_key) + subscription_key: AzureSTT.configuration.subscription_key, + government: AzureSTT.configuration.government) + @client = Client.new(region: region, subscription_key: subscription_key. government: government) end # From 786d3a44446f649407cf878b3710e8fed52ceadf Mon Sep 17 00:00:00 2001 From: Sal Scotto Date: Thu, 24 Oct 2024 12:53:48 -0600 Subject: [PATCH 03/11] Update client.rb government update --- lib/azure_stt/client.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/azure_stt/client.rb b/lib/azure_stt/client.rb index 0518dc3..7d6aa05 100644 --- a/lib/azure_stt/client.rb +++ b/lib/azure_stt/client.rb @@ -8,7 +8,7 @@ module AzureSTT class Client include HTTParty - attr_reader :region, :subscription_key + attr_reader :region, :subscription_key, :government # # Initialize the client @@ -16,10 +16,11 @@ class Client # @param [String] subscription_key Cognitive Services API Key # @param [String] region The region of your resources # - def initialize(region:, subscription_key:) + def initialize(region:, subscription_key:, government:) @subscription_key = subscription_key @region = region - self.class.base_uri "https://#{region}.api.cognitive.microsoft.com/speechtotext/v3.1" + @government = government + self.class.base_uri "https://#{region}.api.cognitive.microsoft.#{government ? 'us' : 'com'}/speechtotext/v3.1" end # From 59d5a8664994e69ebc57c861ddc02beb559ee4b4 Mon Sep 17 00:00:00 2001 From: Sal Scotto Date: Thu, 24 Oct 2024 13:04:30 -0600 Subject: [PATCH 04/11] Update session.rb fixed typo --- lib/azure_stt/session.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/azure_stt/session.rb b/lib/azure_stt/session.rb index f443872..5fced23 100644 --- a/lib/azure_stt/session.rb +++ b/lib/azure_stt/session.rb @@ -20,7 +20,7 @@ class Session def initialize(region: AzureSTT.configuration.region, subscription_key: AzureSTT.configuration.subscription_key, government: AzureSTT.configuration.government) - @client = Client.new(region: region, subscription_key: subscription_key. government: government) + @client = Client.new(region: region, subscription_key: subscription_key, government: government) end # From 24baa4f9a58a025666fa23fe25370fffe924645d Mon Sep 17 00:00:00 2001 From: Sal Scotto Date: Fri, 13 Dec 2024 08:15:48 -0700 Subject: [PATCH 05/11] Update configuration.rb Added a private_link config variabel to allow for private peering --- lib/azure_stt/configuration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/azure_stt/configuration.rb b/lib/azure_stt/configuration.rb index b576887..d6ca88f 100644 --- a/lib/azure_stt/configuration.rb +++ b/lib/azure_stt/configuration.rb @@ -6,7 +6,7 @@ module AzureSTT # the key is in a .env file # class Configuration - attr_accessor :subscription_key, :region, :government + attr_accessor :subscription_key, :region, :government, :private_link end # From 7619e961e52a20543aff6decf2755f93c4f3e0c2 Mon Sep 17 00:00:00 2001 From: Sal Scotto Date: Fri, 13 Dec 2024 08:17:24 -0700 Subject: [PATCH 06/11] Update session.rb added private link passthough --- lib/azure_stt/session.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/azure_stt/session.rb b/lib/azure_stt/session.rb index 5fced23..29239f0 100644 --- a/lib/azure_stt/session.rb +++ b/lib/azure_stt/session.rb @@ -19,8 +19,9 @@ class Session # def initialize(region: AzureSTT.configuration.region, subscription_key: AzureSTT.configuration.subscription_key, - government: AzureSTT.configuration.government) - @client = Client.new(region: region, subscription_key: subscription_key, government: government) + government: AzureSTT.configuration.government, + private_link: AzureSTT.configuration.private_link) + @client = Client.new(region: region, subscription_key: subscription_key, government: government, private_link: private_link) end # From 6045f7f97c13e047d78a310957a867f8183ad280 Mon Sep 17 00:00:00 2001 From: Sal Scotto Date: Fri, 13 Dec 2024 08:19:18 -0700 Subject: [PATCH 07/11] Update client.rb added private link --- lib/azure_stt/client.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/azure_stt/client.rb b/lib/azure_stt/client.rb index 7d6aa05..fd26a1f 100644 --- a/lib/azure_stt/client.rb +++ b/lib/azure_stt/client.rb @@ -8,7 +8,7 @@ module AzureSTT class Client include HTTParty - attr_reader :region, :subscription_key, :government + attr_reader :region, :subscription_key, :government, :private_link # # Initialize the client @@ -16,11 +16,15 @@ class Client # @param [String] subscription_key Cognitive Services API Key # @param [String] region The region of your resources # - def initialize(region:, subscription_key:, government:) + def initialize(region:, subscription_key:, government:, private_link:) @subscription_key = subscription_key @region = region @government = government + @private_link = private_link self.class.base_uri "https://#{region}.api.cognitive.microsoft.#{government ? 'us' : 'com'}/speechtotext/v3.1" + if @private_link.present? + self.class.base_uri "#{@private_link}/speechtotext/v3.1" + end end # From b48a309495428e0808e72a5806b3606cb12758b5 Mon Sep 17 00:00:00 2001 From: Sal Scotto Date: Fri, 13 Dec 2024 08:22:50 -0700 Subject: [PATCH 08/11] Update version.rb added privae lin --- lib/azure_stt/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/azure_stt/version.rb b/lib/azure_stt/version.rb index 6148533..7f88996 100644 --- a/lib/azure_stt/version.rb +++ b/lib/azure_stt/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module AzureStt - VERSION = '0.4.0' + VERSION = '0.4.1' end From d598a671e334ec5d9feda75ad6e60f73dc37adf1 Mon Sep 17 00:00:00 2001 From: Sal Scotto Date: Fri, 8 May 2026 08:26:27 -0400 Subject: [PATCH 09/11] added fallback for private link cert failure --- README.md | 43 ++++++++++++++++++++- env.sample | 6 ++- lib/azure_stt.rb | 7 ++++ lib/azure_stt/client.rb | 70 +++++++++++++++++++++++++--------- lib/azure_stt/configuration.rb | 3 +- lib/azure_stt/session.rb | 13 ++++++- spec/azure_stt/client_spec.rb | 51 +++++++++++++++++++++++++ spec/azure_stt/session_spec.rb | 24 ++++++++++++ 8 files changed, 194 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 90841fa..8733fb1 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,16 @@ Two environment variables are used: - 'SUBSCRIPTION_KEY': the API key you can generate on your Azure account. +Optional environment variables: + +- 'GOVERNMENT': set to `true` to use `microsoft.us` endpoints. + +- 'PRIVATE_LINK': set to your private endpoint base URL (for example `https://a2103tsapp1`). + +- 'SSL_VERIFY_PEER': set to `false` to disable TLS certificate verification (unsafe, but can help with private endpoints that do not have matching certificates). + +- 'SSL_CA_FILE': path to a custom CA bundle used to validate private certificates. + You can look at the file `env.sample` and change the values. If you do not want to use environment variables, you can configure the values like so: @@ -53,13 +63,44 @@ If you do not want to use environment variables, you can configure the values li AzureSTT.configure do |config| config.region = 'your_region' config.subscription_key = 'your_key' + config.government = false + config.private_link = nil + config.ssl_verify_peer = true + config.ssl_ca_file = nil +end +``` + +If your private link certificate hostname does not match the endpoint hostname, requests can fail with `certificate verify failed (Hostname mismatch)`. +You can work around it by disabling peer verification: + +```ruby +AzureSTT.configure do |config| + config.private_link = 'https://a2103tsapp1' + config.ssl_verify_peer = false +end +``` + +Prefer using a private CA when possible: + +```ruby +AzureSTT.configure do |config| + config.private_link = 'https://a2103tsapp1' + config.ssl_verify_peer = true + config.ssl_ca_file = '/path/to/private-ca.pem' end ``` Finally, the class `AzureSTT::Session` uses by the default the values from the configuration, but you can initialize the session with custom values: ```ruby -session = AzureSTT::Session.new(region: 'your_region', subscription_key: 'your_key') +session = AzureSTT::Session.new( + region: 'your_region', + subscription_key: 'your_key', + government: false, + private_link: nil, + ssl_verify_peer: true, + ssl_ca_file: nil +) ``` ### Start a transcription diff --git a/env.sample b/env.sample index 5e31ef2..dd1b251 100644 --- a/env.sample +++ b/env.sample @@ -1,2 +1,6 @@ REGION=centralus -SUBSCRIPTION_KEY=k3ah8ztrc4ojeh98r05zh7v6x9w62lqp \ No newline at end of file +SUBSCRIPTION_KEY=k3ah8ztrc4ojeh98r05zh7v6x9w62lqp +GOVERNMENT=false +PRIVATE_LINK= +SSL_VERIFY_PEER=false +SSL_CA_FILE= diff --git a/lib/azure_stt.rb b/lib/azure_stt.rb index 8702f6c..a66117a 100644 --- a/lib/azure_stt.rb +++ b/lib/azure_stt.rb @@ -4,6 +4,9 @@ # Top level module for AzureSTT # module AzureSTT + def self.env_true?(value) + %w[1 true yes on].include?(value.to_s.strip.downcase) + end end require_relative 'azure_stt/version' @@ -17,4 +20,8 @@ module AzureSTT AzureSTT.configure do |config| config.subscription_key = ENV.fetch('SUBSCRIPTION_KEY', nil) config.region = ENV.fetch('REGION', 'uscentral') + config.government = AzureSTT.env_true?(ENV.fetch('GOVERNMENT', 'false')) + config.private_link = ENV.fetch('PRIVATE_LINK', nil) + config.ssl_verify_peer = AzureSTT.env_true?(ENV.fetch('SSL_VERIFY_PEER', 'false')) + config.ssl_ca_file = ENV.fetch('SSL_CA_FILE', nil) end diff --git a/lib/azure_stt/client.rb b/lib/azure_stt/client.rb index fd26a1f..3c1682e 100644 --- a/lib/azure_stt/client.rb +++ b/lib/azure_stt/client.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'httparty' +require 'openssl' module AzureSTT # # Client class that uses HTTParty to communicate with the API @@ -8,7 +9,8 @@ module AzureSTT class Client include HTTParty - attr_reader :region, :subscription_key, :government, :private_link + attr_reader :region, :subscription_key, :government, :private_link, + :ssl_verify_peer, :ssl_ca_file # # Initialize the client @@ -16,15 +18,18 @@ class Client # @param [String] subscription_key Cognitive Services API Key # @param [String] region The region of your resources # - def initialize(region:, subscription_key:, government:, private_link:) + def initialize(region:, subscription_key:, government: false, private_link: nil, + ssl_verify_peer: true, ssl_ca_file: nil) @subscription_key = subscription_key @region = region @government = government @private_link = private_link - self.class.base_uri "https://#{region}.api.cognitive.microsoft.#{government ? 'us' : 'com'}/speechtotext/v3.1" - if @private_link.present? - self.class.base_uri "#{@private_link}/speechtotext/v3.1" - end + @ssl_verify_peer = ssl_verify_peer + @ssl_ca_file = ssl_ca_file + + base_url = "https://#{region}.api.cognitive.microsoft.#{government ? 'us' : 'com'}" + base_url = @private_link if value_present?(@private_link) + self.class.base_uri "#{base_url.chomp('/')}/speechtotext/v3.1" end # @@ -87,7 +92,9 @@ def get_transcriptions(skip: nil, top: nil) # @return [Boolean] true if the transcription had been deleted, raises an error else # def delete_transcription(id) - response = self.class.delete("/transcriptions/#{id}", headers: headers) + response = with_network_error_handling do + self.class.delete("/transcriptions/#{id}", request_options(headers: headers)) + end handle_response(response) true @@ -116,7 +123,9 @@ def get_transcription_files(id) # @return [Hash] the file parsed # def get_file(file_url) - response = self.class.get(file_url) + response = with_network_error_handling do + HTTParty.get(file_url, request_options) + end results = handle_response(response) @@ -134,12 +143,9 @@ def get_file(file_url) # @return [HTTParty::Response] # def post(path, body) - options = { - headers: headers, - body: body - } - - response = self.class.post(path, options) + response = with_network_error_handling do + self.class.post(path, request_options(headers: headers, body: body)) + end handle_response(response) end @@ -152,13 +158,37 @@ def post(path, body) # @return [HTTParty::Response] # def get(path, parameters = nil) - options = { + response = with_network_error_handling do + self.class.get(path, request_options(headers: headers, query: parameters)) + end + handle_response(response) + end + + def request_options(headers: nil, query: nil, body: nil) + { headers: headers, - query: parameters + query: query, + body: body + }.merge(ssl_options).compact + end + + def ssl_options + return { verify: false } unless ssl_verify_peer + + { + verify: true, + ssl_ca_file: value_present?(ssl_ca_file) ? ssl_ca_file : nil }.compact + end - response = self.class.get(path, options) - handle_response(response) + def with_network_error_handling + yield + rescue OpenSSL::SSL::SSLError => e + raise NetError.new( + code: 0, + message: "SSL connection failed: #{e.message}. " \ + 'Set ssl_verify_peer: false for private endpoints with mismatched certificates, or set ssl_ca_file to trust your private CA.' + ) end # @@ -205,5 +235,9 @@ def headers 'Content-Type' => 'application/json' } end + + def value_present?(value) + !value.nil? && !value.to_s.strip.empty? + end end end diff --git a/lib/azure_stt/configuration.rb b/lib/azure_stt/configuration.rb index d6ca88f..9499f89 100644 --- a/lib/azure_stt/configuration.rb +++ b/lib/azure_stt/configuration.rb @@ -6,7 +6,8 @@ module AzureSTT # the key is in a .env file # class Configuration - attr_accessor :subscription_key, :region, :government, :private_link + attr_accessor :subscription_key, :region, :government, :private_link, + :ssl_verify_peer, :ssl_ca_file end # diff --git a/lib/azure_stt/session.rb b/lib/azure_stt/session.rb index 29239f0..82dcdfc 100644 --- a/lib/azure_stt/session.rb +++ b/lib/azure_stt/session.rb @@ -20,8 +20,17 @@ class Session def initialize(region: AzureSTT.configuration.region, subscription_key: AzureSTT.configuration.subscription_key, government: AzureSTT.configuration.government, - private_link: AzureSTT.configuration.private_link) - @client = Client.new(region: region, subscription_key: subscription_key, government: government, private_link: private_link) + private_link: AzureSTT.configuration.private_link, + ssl_verify_peer: AzureSTT.configuration.ssl_verify_peer, + ssl_ca_file: AzureSTT.configuration.ssl_ca_file) + @client = Client.new( + region: region, + subscription_key: subscription_key, + government: government, + private_link: private_link, + ssl_verify_peer: ssl_verify_peer, + ssl_ca_file: ssl_ca_file + ) end # diff --git a/spec/azure_stt/client_spec.rb b/spec/azure_stt/client_spec.rb index 19048ca..fc0c44d 100644 --- a/spec/azure_stt/client_spec.rb +++ b/spec/azure_stt/client_spec.rb @@ -188,4 +188,55 @@ expect(delete_transcription).to be_truthy end end + + describe 'TLS options' do + let(:id) do + '9c142230-a9e4-4dbb-8cc7-70ca43d5cc91' + end + + let(:response_double) do + instance_double(HTTParty::Response, code: 200, parsed_response: {}) + end + + it 'disables certificate verification when ssl_verify_peer is false' do + insecure_client = described_class.new( + region: 'region', + subscription_key: 'ljdhfkjfh', + ssl_verify_peer: false + ) + + expect(described_class) + .to receive(:get) + .with("/transcriptions/#{id}", hash_including(verify: false)) + .and_return(response_double) + + insecure_client.get_transcription(id) + end + + it 'uses the configured CA file when verification is enabled' do + ca_file = '/tmp/private-ca.pem' + secure_client = described_class.new( + region: 'region', + subscription_key: 'ljdhfkjfh', + ssl_verify_peer: true, + ssl_ca_file: ca_file + ) + + expect(described_class) + .to receive(:get) + .with('/transcriptions', hash_including(verify: true, ssl_ca_file: ca_file)) + .and_return(response_double) + + secure_client.get_transcriptions + end + + it 'wraps SSL errors as AzureSTT::NetError' do + allow(described_class) + .to receive(:get) + .and_raise(OpenSSL::SSL::SSLError, 'hostname mismatch') + + expect { client.get_transcription(id) } + .to raise_error(AzureSTT::NetError, /hostname mismatch/) + end + end end diff --git a/spec/azure_stt/session_spec.rb b/spec/azure_stt/session_spec.rb index 820267b..5f89945 100644 --- a/spec/azure_stt/session_spec.rb +++ b/spec/azure_stt/session_spec.rb @@ -17,6 +17,30 @@ .and_return(client) end + describe '#initialize' do + it 'passes endpoint and TLS options to the client' do + described_class.new( + region: 'usgovvirginia', + subscription_key: 'dfhd', + government: true, + private_link: 'https://a2103tsapp1', + ssl_verify_peer: false, + ssl_ca_file: '/tmp/private-ca.pem' + ) + + expect(AzureSTT::Client) + .to have_received(:new) + .with( + region: 'usgovvirginia', + subscription_key: 'dfhd', + government: true, + private_link: 'https://a2103tsapp1', + ssl_verify_peer: false, + ssl_ca_file: '/tmp/private-ca.pem' + ) + end + end + describe '#create_transcription' do subject(:create_transcription) do session.create_transcription(**params) From 4f3efdcff556cae35c2e99e84ea24052fd3277c9 Mon Sep 17 00:00:00 2001 From: Sal Scotto Date: Fri, 8 May 2026 08:26:46 -0400 Subject: [PATCH 10/11] added fallback for private link cert failure --- lib/azure_stt/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/azure_stt/version.rb b/lib/azure_stt/version.rb index 7f88996..d326475 100644 --- a/lib/azure_stt/version.rb +++ b/lib/azure_stt/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module AzureStt - VERSION = '0.4.1' + VERSION = '0.4.2' end From c7e4846d3ffa8ee4ffa138d375a2e11f4acc7c42 Mon Sep 17 00:00:00 2001 From: Sal Scotto Date: Fri, 8 May 2026 09:10:49 -0400 Subject: [PATCH 11/11] added fallback for private link cert failure --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index abad609..2ae2a4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ The format is based on [Keep a Changelog][Keep a Changelog] and this project adh ## [Unreleased] +### [0.4.2] - 2026-05-08 + +### Fixed + +-ssl verify option and cert file location for non valid private links in gov + ## [0.4.0] - 2023-05-10 ### Fixed