diff --git a/Gemfile.lock b/Gemfile.lock index 99e4b61e..d10e88d6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -75,8 +75,13 @@ GIT virtus GEM - remote: https://rubygems.org/ remote: https://gems.contribsys.com/ + specs: + sidekiq-pro (3.4.0) + sidekiq (>= 4.1.5) + +GEM + remote: https://rubygems.org/ specs: HDRHistogram (0.1.3) activemodel (4.2.10) @@ -158,7 +163,7 @@ GEM coderay (~> 1.1.0) method_source (~> 0.9.0) public_suffix (2.0.5) - rack (2.2.3) + rack (2.2.6.4) rack-protection (2.0.1) rack rake (13.0.1) @@ -189,8 +194,6 @@ GEM connection_pool (~> 2.2, >= 2.2.0) rack-protection (>= 1.5.0) redis (~> 3.2, >= 3.2.1) - sidekiq-pro (3.4.0) - sidekiq (>= 4.1.5) thread_safe (0.3.6) travis-config (1.1.3) hashr (~> 2.0) diff --git a/lib/travis/scheduler/record.rb b/lib/travis/scheduler/record.rb index 32c95524..9c5e3e0c 100644 --- a/lib/travis/scheduler/record.rb +++ b/lib/travis/scheduler/record.rb @@ -3,9 +3,11 @@ require 'travis/scheduler/record/branch' require 'travis/scheduler/record/build' require 'travis/scheduler/record/commit' +require 'travis/scheduler/record/custom_key' require 'travis/scheduler/record/installation' require 'travis/scheduler/record/job' require 'travis/scheduler/record/log' +require 'travis/scheduler/record/membership' require 'travis/scheduler/record/organization' require 'travis/scheduler/record/permission' require 'travis/scheduler/record/pull_request' diff --git a/lib/travis/scheduler/record/custom_key.rb b/lib/travis/scheduler/record/custom_key.rb new file mode 100644 index 00000000..a4c7ae64 --- /dev/null +++ b/lib/travis/scheduler/record/custom_key.rb @@ -0,0 +1,7 @@ +require 'travis/support/encrypted_column' +require 'travis/support/secure_config' + +class CustomKey < ActiveRecord::Base + serialize :private_key, Travis::EncryptedColumn.new + belongs_to :owner, polymorphic: true +end diff --git a/lib/travis/scheduler/record/membership.rb b/lib/travis/scheduler/record/membership.rb new file mode 100644 index 00000000..1812894c --- /dev/null +++ b/lib/travis/scheduler/record/membership.rb @@ -0,0 +1,4 @@ +class Membership < ActiveRecord::Base + belongs_to :user + belongs_to :organization +end diff --git a/lib/travis/scheduler/serialize/worker.rb b/lib/travis/scheduler/serialize/worker.rb index 9f87de05..6fbc89bb 100644 --- a/lib/travis/scheduler/serialize/worker.rb +++ b/lib/travis/scheduler/serialize/worker.rb @@ -19,7 +19,7 @@ def data vm_size: job.vm_size, queue: job.queue, config: job.decrypted_config, - env_vars: job.env_vars, + env_vars: env_vars_with_custom_keys, job: job_data, host: Travis::Scheduler.config.host, source: build_data, @@ -238,6 +238,39 @@ def allowed_repositories def travis_vcs_proxy? repo.vcs_type == 'TravisproxyRepository' end + + def env_vars_with_custom_keys + job.env_vars + custom_keys + end + + def custom_keys + return [] if job.decrypted_config[:keys].blank? + + if job.source.event_type == 'pull_request' && job.source.request.pull_request.head_repo_slug != job.source.request.pull_request.base_repo_slug + base_repo_owner_name, base_repo_name = job.source.request.pull_request.base_repo_slug.to_s.split('/') + return [] unless base_repo_owner_name && base_repo_name + + + base_repo = ::Repository.find_by(owner_name: base_repo_owner_name, name: base_repo_name) + return [] unless base_repo && base_repo.settings.share_ssh_keys_with_forks? + + end + + job.decrypted_config[:keys].map do |key| + custom_key = CustomKey.where(name: key, owner_id: build.sender_id, owner_type: 'User').first + if custom_key.nil? + org_ids = Membership.where(user_id: build.sender_id).map(&:organization_id) + if !base_repo.nil? && base_repo.owner_type == 'Organization' + org_ids.reject! { |id| id != base_repo.owner_id } + elsif repo.owner_type == 'Organization' + org_ids.reject! { |id| id != repo.owner_id } + end + + custom_key = CustomKey.where(name: key, owner_id: org_ids, owner_type: 'Organization').first unless org_ids.empty? + end + custom_key.nil? ? nil : { name: "TRAVIS_#{key}", value: Base64.strict_encode64(custom_key.private_key), public: false, branch: nil } + end.compact + end end end end diff --git a/lib/travis/scheduler/serialize/worker/config/decrypt.rb b/lib/travis/scheduler/serialize/worker/config/decrypt.rb index 160bc039..0a9439b6 100644 --- a/lib/travis/scheduler/serialize/worker/config/decrypt.rb +++ b/lib/travis/scheduler/serialize/worker/config/decrypt.rb @@ -9,12 +9,18 @@ def apply config[key] = process_env(config[key]) if config[key] end + force_vault_to_be_secure!(config) + config[:vault] = decryptor.decrypt(config[:vault]) if config[:vault] config[:addons] = decryptor.decrypt(config[:addons]) if config[:addons] config end private + def force_vault_to_be_secure!(config) + config[:vault].delete(:token) if config.dig(:vault, :token).is_a?(String) + end + def secure_env? !!options[:secure_env] end diff --git a/lib/travis/scheduler/serialize/worker/job.rb b/lib/travis/scheduler/serialize/worker/job.rb index 45200c49..452528d4 100644 --- a/lib/travis/scheduler/serialize/worker/job.rb +++ b/lib/travis/scheduler/serialize/worker/job.rb @@ -19,7 +19,7 @@ def env_vars end def secure_env? - defined?(@secure_env) ? @secure_env : @secure_env = !pull_request? || secure_env_allowed_in_pull_request? + defined?(@secure_env) ? @secure_env : (@secure_env = (!pull_request? || secure_env_allowed_in_pull_request?)) end def pull_request? diff --git a/lib/travis/scheduler/serialize/worker/repo.rb b/lib/travis/scheduler/serialize/worker/repo.rb index 6e1fd6e2..552a2c1a 100644 --- a/lib/travis/scheduler/serialize/worker/repo.rb +++ b/lib/travis/scheduler/serialize/worker/repo.rb @@ -56,6 +56,14 @@ def travis_vcs_proxy? vcs_type == 'TravisproxyRepository' end + def owner_type + repo.owner_type + end + + def owner_id + repo.owner_id + end + private # If the repo does not have a custom timeout, look to the repo's diff --git a/spec/support/factories.rb b/spec/support/factories.rb index ff636b6a..c02eb35e 100644 --- a/spec/support/factories.rb +++ b/spec/support/factories.rb @@ -87,5 +87,14 @@ def public=(value) author_name 'Sven Fuchs' author_email 'me@svenfuchs.com' end + + factory :membership do + association :organization + association :user + end + + factory :custom_key, :class => 'CustomKey' do + name 'key' + end end diff --git a/spec/travis/scheduler/serialize/worker/config_spec.rb b/spec/travis/scheduler/serialize/worker/config_spec.rb index aec1cfb0..fe2fd553 100644 --- a/spec/travis/scheduler/serialize/worker/config_spec.rb +++ b/spec/travis/scheduler/serialize/worker/config_spec.rb @@ -71,6 +71,16 @@ def encrypt(string) let(:env) { [{ FOO: 'foo', BAR: 'bar' }, encrypt('BAZ=baz')] } it { should eql env: ['FOO=foo', 'BAR=bar', 'SECURE BAZ=baz'], global_env: ['FOO=foo', 'BAR=bar', 'SECURE BAZ=baz'] } end + + describe 'decrypts vault secure token' do + let(:config) { { vault: { token: { secure: encrypt('my_key') } } } } + it { should eql vault: {token: 'my_key'} } + end + + describe 'clears vault unsecure token' do + let(:config) { { vault: { token: 'my_key' } } } + it { should eql vault: {} } + end end describe 'with secure env disabled' do diff --git a/spec/travis/scheduler/serialize/worker_spec.rb b/spec/travis/scheduler/serialize/worker_spec.rb index 2cc084a4..4371fbf5 100644 --- a/spec/travis/scheduler/serialize/worker_spec.rb +++ b/spec/travis/scheduler/serialize/worker_spec.rb @@ -449,4 +449,45 @@ def encrypted(value) it { expect(data[:keep_netrc]).to be false } end end + + context 'custom_keys' do + let!(:organization1) {FactoryGirl.create(:org, login: "org1", id: 1)} + let!(:organization2) {FactoryGirl.create(:org, login: "org2", id: 2)} + let!(:repo) { FactoryGirl.create(:repo, default_branch: 'main') } + let!(:membership1) {FactoryGirl.create(:membership, user: repo.owner, organization: organization1) } + let!(:membership2) {FactoryGirl.create(:membership, user: repo.owner, organization: organization2) } + let!(:custom_key1) {FactoryGirl.create(:custom_key, name: 'key1', owner_id: organization1.id, owner_type: 'Organization', private_key: 'abc')} + let!(:custom_key2) {FactoryGirl.create(:custom_key, name: 'key1', owner_id: organization2.id, owner_type: 'Organization', private_key: 'def')} + + describe 'when two organization have the same key name' do + before { + build.update(sender_id: repo.owner.id) + job.update(config: {:keys => ['key1']}) + repo.update_attributes(owner: organization2, owner_name: 'org2') + } + + it { expect(data[:env_vars]).to include({:name=>"TRAVIS_key1", :value=>"ZGVm", :public=>false, :branch=>nil})} + end + + describe 'when user has no access to organization' do + let!(:organization3) {FactoryGirl.create(:org, login: "org3", id: 3)} + let!(:custom_key3) {FactoryGirl.create(:custom_key, name: 'key1', owner_id: organization3.id, owner_type: 'Organization', private_key: 'ghi')} + let(:raw_settings) do + { + env_vars: nil, + timeout_hard_limit: 180, + timeout_log_silence: 20, + share_ssh_keys_with_forks: false + } + end + + before { + build.update(sender_id: repo.owner.id) + job.update(config: {:keys => ['key1']}) + repo.update_attributes(owner: organization3, owner_name: 'org3') + } + + it { expect(data[:env_vars]).to eq([])} + end + end end