FEATURE: global setting for whitelisted IP (#5)

FEATURE: global setting for whitelisted IP (#5)

Adds support for prometheus_trusted_ip_whitelist_regex which allows clients to define special IP addresses with access to prom routes

diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..9a69503
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,12 @@
+# We want to use the KVM-based system, so require sudo
+sudo: required
+services:
+  - docker
+
+before_install:
+  - git clone --depth=1 https://github.com/discourse/discourse-plugin-ci
+
+install: true # Prevent travis doing bundle install
+
+script:
+  - discourse-plugin-ci/script.sh
diff --git a/lib/middleware/metrics.rb b/lib/middleware/metrics.rb
index 7861f34..681b1b4 100644
--- a/lib/middleware/metrics.rb
+++ b/lib/middleware/metrics.rb
@@ -24,10 +24,22 @@ module DiscoursePrometheus
 
     def is_private_ip?(env)
       request = Rack::Request.new(env)
-      ip = IPAddr.new(request.ip) rescue nil
+      ip = IPAddr.new(request.ip) rescue false
       !!(ip && ip.to_s =~ PRIVATE_IP)
     end
 
+    def is_trusted_ip?(env)
+      return false if GlobalSetting.prometheus_trusted_ip_whitelist_regex.empty?
+      begin
+        trusted_ip_regex = Regexp.new GlobalSetting.prometheus_trusted_ip_whitelist_regex
+        request = Rack::Request.new(env)
+        ip = IPAddr.new(request.ip)
+      rescue
+        false
+      end
+      !!(trusted_ip_regex && ip && ip.to_s =~ trusted_ip_regex)
+    end
+
     def is_admin?(env)
       host = RailsMultisite::ConnectionManagement.host(env)
       result = false
@@ -40,7 +52,7 @@ module DiscoursePrometheus
 
     def intercept?(env)
       if env["PATH_INFO"] == "/metrics"
-        return is_private_ip?(env) || is_admin?(env)
+        return is_private_ip?(env) || is_trusted_ip?(env) || is_admin?(env)
       end
       false
     end
@@ -48,9 +60,9 @@ module DiscoursePrometheus
     def metrics(env)
       data = Net::HTTP.get(URI("http://localhost:#{GlobalSetting.prometheus_collector_port}/metrics"))
       [200, {
-        "Content-Type" => "text/plain; charset=utf-8",
-        "Content-Length" => data.bytesize.to_s
-      }, [data]]
+         "Content-Type" => "text/plain; charset=utf-8",
+         "Content-Length" => data.bytesize.to_s
+       }, [data]]
     end
 
   end
diff --git a/plugin.rb b/plugin.rb
index e00003b..67e4648 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -29,6 +29,7 @@ require_relative("lib/demon")
 require_relative("lib/middleware/metrics")
 
 GlobalSetting.add_default :prometheus_collector_port, 9405
+GlobalSetting.add_default :prometheus_trusted_ip_whitelist_regex, ''
 
 Rails.configuration.middleware.unshift DiscoursePrometheus::Middleware::Metrics
 
diff --git a/spec/middleware/metrics_spec.rb b/spec/middleware/metrics_spec.rb
index ce622a6..ece6bd7 100644
--- a/spec/middleware/metrics_spec.rb
+++ b/spec/middleware/metrics_spec.rb
@@ -9,13 +9,41 @@ describe ::DiscoursePrometheus::Middleware::Metrics do
     ::DiscoursePrometheus::Middleware::Metrics.new(app)
   end
 
+  it "will 404 for unauthed if prometheus_trusted_ip_whitelist_regex is unset" do
+    status, = middleware.call("PATH_INFO" => '/metrics', "REMOTE_ADDR" => '200.0.1.1', "rack.input" => StringIO.new)
+    expect(status).to eq(404)
+  end
+
   it "will 404 for unauthed" do
     status, = middleware.call("PATH_INFO" => '/metrics', "REMOTE_ADDR" => '200.0.1.1', "rack.input" => StringIO.new)
     expect(status).to eq(404)
   end
 
+  it "will 404 for unauthed and invalid regex" do
+    GlobalSetting.stubs(:prometheus_trusted_ip_whitelist_regex).returns("unbalanced bracket[")
+    status, = middleware.call("PATH_INFO" => '/metrics', "REMOTE_ADDR" => '200.0.1.1', "rack.input" => StringIO.new)
+    expect(status).to eq(404)
+  end
+
+  it "will 404 for unauthed empty regex" do
+    status, = middleware.call("PATH_INFO" => '/metrics', "REMOTE_ADDR" => '200.0.1.1', "rack.input" => StringIO.new)
+    expect(status).to eq(404)
+  end
+
   it "can proxy the dedicated port" do
+    stub_request(:get, "http://localhost:#{GlobalSetting.prometheus_collector_port}/metrics").
+      to_return(status: 200, body: "hello world", headers: {})
+
+    status, headers, body = middleware.call("PATH_INFO" => '/metrics', "REMOTE_ADDR" => '192.168.1.1')
+    body = body.join
+
+    expect(status).to eq(200)
+    expect(headers["Content-Type"]).to eq('text/plain; charset=utf-8')
+    expect(body).to include('hello world')
+  end
 
+  it "can proxy the dedicated port even with invalid regex" do
+    GlobalSetting.stubs(:prometheus_trusted_ip_whitelist_regex).returns("unbalanced bracket[")
     stub_request(:get, "http://localhost:#{GlobalSetting.prometheus_collector_port}/metrics").
       to_return(status: 200, body: "hello world", headers: {})
 
@@ -26,4 +54,17 @@ describe ::DiscoursePrometheus::Middleware::Metrics do
     expect(headers["Content-Type"]).to eq('text/plain; charset=utf-8')
     expect(body).to include('hello world')
   end
+
+  it "can proxy the dedicated port on trusted IP" do
+    GlobalSetting.stubs(:prometheus_trusted_ip_whitelist_regex).returns("(200\.0)")
+    stub_request(:get, "http://localhost:#{GlobalSetting.prometheus_collector_port}/metrics").
+      to_return(status: 200, body: "hello world", headers: {})
+
+    status, headers, body = middleware.call("PATH_INFO" => '/metrics', "REMOTE_ADDR" => '200.0.0.1')
+    body = body.join
+
+    expect(status).to eq(200)
+    expect(headers["Content-Type"]).to eq('text/plain; charset=utf-8')
+    expect(body).to include('hello world')
+  end
 end

GitHub sha: 5af25b8d

This commit appears in #5 which was merged by SamSaffron.