Skip to content

Commit 5f43948

Browse files
authored
Fix misleading Terraform registry error when TLS certificate verification fails (#15131)
When a private Terraform registry uses a certificate signed by an unknown authority, the Dependabot proxy cannot establish the MITM TLS connection and returns a non-200 response. Previously, the services method silently treated any non-200 response as "no service discovery supported", causing the unrelated error "Host does not support required Terraform-native service". Fix the services method to raise PrivateSourceBadResponse for unexpected status codes (5xx, etc.) and PrivateSourceAuthenticationFailure for 401, reserving the "does not support" message for genuine 404 responses. Also detect TLS/certificate-related keywords in Excon::Error::Socket exceptions and raise PrivateSourceCertificateFailure to surface the actual certificate issue to the user. Fixes #15089
1 parent 10db257 commit 5f43948

2 files changed

Lines changed: 55 additions & 4 deletions

File tree

terraform/lib/dependabot/terraform/registry_client.rb

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ class RegistryClient
2121
T::Array[String]
2222
)
2323
PUBLIC_HOSTNAME = "registry.terraform.io"
24+
CERTIFICATE_ERROR_KEYWORDS = T.let(
25+
%w(certificate SSL x509 verify).freeze,
26+
T::Array[String]
27+
)
2428

2529
sig { params(hostname: String, credentials: T::Array[Dependabot::Credential]).void }
2630
def initialize(hostname: PUBLIC_HOSTNAME, credentials: [])
@@ -176,10 +180,14 @@ def services
176180
@services ||= T.let(
177181
begin
178182
response = http_get(url_for("/.well-known/terraform.json"))
179-
if response.status == 200 && !response.body.empty?
180-
JSON.parse(response.body)
181-
else
183+
if response.status == 200
184+
response.body.empty? ? {} : JSON.parse(response.body)
185+
elsif response.status == 404
182186
{}
187+
elsif response.status == 401
188+
raise PrivateSourceAuthenticationFailure, hostname
189+
else
190+
raise PrivateSourceBadResponse, hostname
183191
end
184192
rescue JSON::ParserError => e
185193
Dependabot.logger.warn("Failed to parse Terraform registry services: #{e.message}")
@@ -207,7 +215,11 @@ def http_get(url)
207215
url: url.to_s,
208216
headers: headers_for(hostname)
209217
)
210-
rescue Excon::Error::Socket, Excon::Error::Timeout
218+
rescue Excon::Error::Socket => e
219+
raise PrivateSourceCertificateFailure, hostname if certificate_error?(e.message)
220+
221+
raise PrivateSourceBadResponse, hostname
222+
rescue Excon::Error::Timeout
211223
raise PrivateSourceBadResponse, hostname
212224
end
213225

@@ -240,6 +252,11 @@ def url_for(path)
240252
def error(message)
241253
Dependabot::DependabotError.new(message)
242254
end
255+
256+
sig { params(message: String).returns(T::Boolean) }
257+
def certificate_error?(message)
258+
CERTIFICATE_ERROR_KEYWORDS.any? { |keyword| message.include?(keyword) }
259+
end
243260
end
244261
end
245262
end

terraform/spec/dependabot/terraform/registry_client_spec.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,40 @@
299299
end
300300
end
301301

302+
context "when the metadata endpoint raises a certificate error" do
303+
it "raises PrivateSourceCertificateFailure" do
304+
stub_request(:get, metadata).to_raise(
305+
Excon::Error::Socket.new(
306+
StandardError.new("SSL_connect returned=1 errno=0 state=error: certificate verify failed")
307+
)
308+
)
309+
310+
expect do
311+
client.service_url_for("modules.v1")
312+
end.to raise_error(Dependabot::PrivateSourceCertificateFailure)
313+
end
314+
end
315+
316+
context "when the metadata endpoint returns a 5xx error" do
317+
it "raises PrivateSourceBadResponse instead of the misleading service error" do
318+
stub_request(:get, metadata).and_return(status: 502)
319+
320+
expect do
321+
client.service_url_for("modules.v1")
322+
end.to raise_error(Dependabot::PrivateSourceBadResponse)
323+
end
324+
end
325+
326+
context "when the metadata endpoint returns a 401 error" do
327+
it "raises PrivateSourceAuthenticationFailure" do
328+
stub_request(:get, metadata).and_return(status: 401)
329+
330+
expect do
331+
client.service_url_for("modules.v1")
332+
end.to raise_error(Dependabot::PrivateSourceAuthenticationFailure)
333+
end
334+
end
335+
302336
context "when the service url is not available" do
303337
it "raises an error" do
304338
stub_request(:get, metadata).and_return(body: { "modules.v1": "/v1/modules/" }.to_json)

0 commit comments

Comments
 (0)