From fa62cca03bfe988f35c435dc9c4b8a284e33b560 Mon Sep 17 00:00:00 2001 From: Michael Fraenkel Date: Mon, 29 Feb 2016 16:39:41 -0800 Subject: [PATCH] Allow specifying a reserved set of private domains --- Gemfile | 1 + Gemfile.lock | 2 ++ app/models/runtime/private_domain.rb | 23 +++++++++++++ bosh-templates/cloud_controller_api.yml.erb | 3 ++ bosh-templates/cloud_controller_clock.yml.erb | 3 ++ .../cloud_controller_worker.yml.erb | 3 ++ lib/cloud_controller/config.rb | 6 +++- .../config/reserved_private_domains.dat | 11 +++++++ spec/unit/lib/cloud_controller/config_spec.rb | 7 ++++ .../models/runtime/private_domain_spec.rb | 32 +++++++++++++++++++ 10 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/config/reserved_private_domains.dat diff --git a/Gemfile b/Gemfile index 64cf1ac6491..fcef7843cfe 100644 --- a/Gemfile +++ b/Gemfile @@ -32,6 +32,7 @@ gem 'statsd-ruby' gem 'activemodel', '~> 4.2.5.2' gem 'actionpack', '~> 4.2.5.2' gem 'actionview', '~> 4.2.5.2' +gem 'public_suffix' # We need to use https for git urls as the git protocol is blocked by various # firewalls diff --git a/Gemfile.lock b/Gemfile.lock index 81de085d6d7..def15bc69e0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -256,6 +256,7 @@ GEM pry-byebug (3.1.0) byebug (~> 4.0) pry (~> 0.10) + public_suffix (1.5.3) rack (1.6.4) rack-protection (1.5.3) rack @@ -416,6 +417,7 @@ DEPENDENCIES parallel_tests pg (= 0.16.0) pry-byebug + public_suffix rack-test railties (~> 4.2.5.2) rake diff --git a/app/models/runtime/private_domain.rb b/app/models/runtime/private_domain.rb index ed90ac60476..c57ed0b5f4a 100644 --- a/app/models/runtime/private_domain.rb +++ b/app/models/runtime/private_domain.rb @@ -1,4 +1,5 @@ require 'models/runtime/domain' +require 'public_suffix' module VCAP::CloudController class PrivateDomain < Domain @@ -39,6 +40,8 @@ def as_summary_json def validate super + errors.add(:name, :reserved) if reserved? + validates_presence :owning_organization exclude_domains_from_same_org = Domain.dataset. exclude(owning_organization_id: owning_organization_id). @@ -70,6 +73,21 @@ def shared? false end + class << self + def configure(filename) + list = nil + if filename + File.open(filename) do |f| + list = PublicSuffix::List.parse(f) + end + else + list = PublicSuffix::List.new + end + + PublicSuffix::List.default = list + end + end + private def shared_by?(org) @@ -84,5 +102,10 @@ def validate_total_private_domains errors.add(:organization, :total_private_domains_exceeded) end end + + def reserved? + rule = PublicSuffix::List.default.find(name) + !rule.nil? && !rule.allow?(name) + end end end diff --git a/bosh-templates/cloud_controller_api.yml.erb b/bosh-templates/cloud_controller_api.yml.erb index 60dc8739591..c672d19123c 100644 --- a/bosh-templates/cloud_controller_api.yml.erb +++ b/bosh-templates/cloud_controller_api.yml.erb @@ -38,6 +38,9 @@ external_domain: <%= p("cc.external_host") %>.<%= p("domain") %> system_domain_organization: <%= p("system_domain_organization") %> system_domain: <%= p("system_domain") %> app_domains: [ <%= p("app_domains").join(", ") %> ] +<% if_p("cc.reserved_private_domains") do |reserved| %> +reserved_private_domains: <%= reserved %> +<% end %> jobs: global: diff --git a/bosh-templates/cloud_controller_clock.yml.erb b/bosh-templates/cloud_controller_clock.yml.erb index 9f3ce9507c4..b568a9e9e16 100644 --- a/bosh-templates/cloud_controller_clock.yml.erb +++ b/bosh-templates/cloud_controller_clock.yml.erb @@ -38,6 +38,9 @@ external_domain: <%= p("cc.external_host") %>.<%= p("domain") %> system_domain_organization: <%= p("system_domain_organization") %> system_domain: <%= p("system_domain") %> app_domains: [ <%= p("app_domains").join(", ") %> ] +<% if_p("cc.reserved_private_domains") do |reserved| %> +reserved_private_domains: <%= reserved %> +<% end %> jobs: global: diff --git a/bosh-templates/cloud_controller_worker.yml.erb b/bosh-templates/cloud_controller_worker.yml.erb index 7ffc0a77a77..81070cca15f 100644 --- a/bosh-templates/cloud_controller_worker.yml.erb +++ b/bosh-templates/cloud_controller_worker.yml.erb @@ -38,6 +38,9 @@ external_domain: <%= p("cc.external_host") %>.<%= p("domain") %> system_domain_organization: <%= p("system_domain_organization") %> system_domain: <%= p("system_domain") %> app_domains: [ <%= p("app_domains").join(", ") %> ] +<% if_p("cc.reserved_private_domains") do |reserved| %> +reserved_private_domains: <%= reserved %> +<% end %> jobs: global: diff --git a/lib/cloud_controller/config.rb b/lib/cloud_controller/config.rb index 45da8f13648..69a95ac9115 100644 --- a/lib/cloud_controller/config.rb +++ b/lib/cloud_controller/config.rb @@ -242,7 +242,9 @@ class Config < VCAP::Config routing_client_secret: String, }, - optional(:route_services_enabled) => bool + optional(:route_services_enabled) => bool, + + optional(:reserved_private_domains) => String, } end @@ -264,6 +266,8 @@ def configure_components(config) QuotaDefinition.configure(config) Stack.configure(config[:stacks_file]) + PrivateDomain.configure(config[:reserved_private_domains]) + dependency_locator = CloudController::DependencyLocator.instance nsync_client = Diego::NsyncClient.new(@config) dependency_locator.register(:nsync_client, nsync_client) diff --git a/spec/fixtures/config/reserved_private_domains.dat b/spec/fixtures/config/reserved_private_domains.dat new file mode 100644 index 00000000000..86f18022bd1 --- /dev/null +++ b/spec/fixtures/config/reserved_private_domains.dat @@ -0,0 +1,11 @@ +// ===BEGIN ICANN DOMAINS=== + +// ac : https://en.wikipedia.org/wiki/.ac +ac +com.ac + +// xn--jvr189m : 2015-02-26 Amazon EU S.à r.l. +手机 + +*.wild.card +!a.wild.card diff --git a/spec/unit/lib/cloud_controller/config_spec.rb b/spec/unit/lib/cloud_controller/config_spec.rb index c8010a6845e..03229ebf8bc 100644 --- a/spec/unit/lib/cloud_controller/config_spec.rb +++ b/spec/unit/lib/cloud_controller/config_spec.rb @@ -270,6 +270,8 @@ module VCAP::CloudController password: 'password', }, }, + + reserved_private_domains: File.join(Paths::FIXTURES, 'config/reserved_private_domains.dat'), } end @@ -418,6 +420,11 @@ module VCAP::CloudController Config.configure_components(@test_config) expect(dependency_locator.stager_client).to be_an_instance_of(VCAP::CloudController::Diego::StagerClient) end + + it 'sets up the reserved private domain' do + expect(PrivateDomain).to receive(:configure).with(@test_config[:reserved_private_domains]) + Config.configure_components(@test_config) + end end end end diff --git a/spec/unit/models/runtime/private_domain_spec.rb b/spec/unit/models/runtime/private_domain_spec.rb index 19c55174a2f..9ae1df79860 100644 --- a/spec/unit/models/runtime/private_domain_spec.rb +++ b/spec/unit/models/runtime/private_domain_spec.rb @@ -3,6 +3,11 @@ module VCAP::CloudController describe PrivateDomain, type: :model do let(:private_domain) { described_class.make name: 'test.example.com' } + let(:reserved) { nil } + + before(:each) do + TestConfig.override({ reserved_private_domains: reserved }) + end it { is_expected.to have_timestamp_columns } @@ -70,6 +75,33 @@ module VCAP::CloudController expect { PrivateDomain.make name: 'bar.foo.com' }.to raise_error(Sequel::ValidationFailed, /overlapping_domain/) end + context 'with reserved private domains' do + let(:reserved) { File.join(Paths::FIXTURES, 'config/reserved_private_domains.dat') } + + it 'handles normal reserved domain names' do + expect { PrivateDomain.make name: 'com.ac' }.to raise_error(Sequel::ValidationFailed, /reserved/) + expect { PrivateDomain.make name: 'a.com.ac' }.to_not raise_error + expect { PrivateDomain.make name: 'scom.ac' }.to_not raise_error + end + + it 'handles wildcard reserved domain names' do + expect { PrivateDomain.make name: 'b.wild.card' }.to raise_error(Sequel::ValidationFailed, /reserved/) + expect { PrivateDomain.make name: 'a.b.wild.card' }.to_not raise_error + end + + it 'handles exception reserved domain names' do + expect { PrivateDomain.make name: 'a.wild.card' }.to_not raise_error + end + + context 'with a missing file' do + let(:reserved) { nil } + + it 'raises an error' do + expect { PrivateDomain.configure('bogus') }.to raise_error(Errno::ENOENT) + end + end + end + describe 'total allowed private domains' do let(:organization) { Organization.make } let(:org_quota) { organization.quota_definition }