Commit ab72b0b6 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'qa-add-more-key-tests' into 'master'

Add more QA SSH key tests

Closes gitlab-qa#183

See merge request gitlab-org/gitlab-ce!17662
parents df51cf69 6c21b502
...@@ -17,14 +17,14 @@ ...@@ -17,14 +17,14 @@
.ci-variable-row-body .ci-variable-row-body
%input.js-ci-variable-input-id{ type: "hidden", name: id_input_name, value: id } %input.js-ci-variable-input-id{ type: "hidden", name: id_input_name, value: id }
%input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name } %input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name }
%input.js-ci-variable-input-key.ci-variable-body-item.form-control{ type: "text", %input.js-ci-variable-input-key.ci-variable-body-item.qa-ci-variable-input-key.form-control{ type: "text",
name: key_input_name, name: key_input_name,
value: key, value: key,
placeholder: s_('CiVariables|Input variable key') } placeholder: s_('CiVariables|Input variable key') }
.ci-variable-body-item .ci-variable-body-item
.form-control.js-secret-value-placeholder{ class: ('hide' unless id) } .form-control.js-secret-value-placeholder.qa-ci-variable-input-value{ class: ('hide' unless id) }
= '*' * 20 = '*' * 20
%textarea.js-ci-variable-input-value.js-secret-value.form-control{ class: ('hide' if id), %textarea.js-ci-variable-input-value.js-secret-value.qa-ci-variable-input-value.form-control{ class: ('hide' if id),
rows: 1, rows: 1,
name: value_input_name, name: value_input_name,
placeholder: s_('CiVariables|Input variable value') } placeholder: s_('CiVariables|Input variable value') }
......
FROM ruby:2.4 FROM ruby:2.4-stretch
LABEL maintainer "Grzegorz Bizon <grzegorz@gitlab.com>" LABEL maintainer "Grzegorz Bizon <grzegorz@gitlab.com>"
ENV DEBIAN_FRONTEND noninteractive ENV DEBIAN_FRONTEND noninteractive
......
...@@ -6,5 +6,4 @@ gem 'capybara-screenshot', '~> 1.0.18' ...@@ -6,5 +6,4 @@ gem 'capybara-screenshot', '~> 1.0.18'
gem 'rake', '~> 12.3.0' gem 'rake', '~> 12.3.0'
gem 'rspec', '~> 3.7' gem 'rspec', '~> 3.7'
gem 'selenium-webdriver', '~> 3.8.0' gem 'selenium-webdriver', '~> 3.8.0'
gem 'net-ssh', require: false
gem 'airborne', '~> 0.2.13' gem 'airborne', '~> 0.2.13'
...@@ -46,7 +46,6 @@ GEM ...@@ -46,7 +46,6 @@ GEM
mini_mime (1.0.0) mini_mime (1.0.0)
mini_portile2 (2.3.0) mini_portile2 (2.3.0)
minitest (5.11.1) minitest (5.11.1)
net-ssh (4.1.0)
netrc (0.11.0) netrc (0.11.0)
nokogiri (1.8.1) nokogiri (1.8.1)
mini_portile2 (~> 2.3.0) mini_portile2 (~> 2.3.0)
...@@ -98,7 +97,6 @@ DEPENDENCIES ...@@ -98,7 +97,6 @@ DEPENDENCIES
airborne (~> 0.2.13) airborne (~> 0.2.13)
capybara (~> 2.16.1) capybara (~> 2.16.1)
capybara-screenshot (~> 1.0.18) capybara-screenshot (~> 1.0.18)
net-ssh
pry-byebug (~> 3.5.1) pry-byebug (~> 3.5.1)
rake (~> 12.3.0) rake (~> 12.3.0)
rspec (~> 3.7) rspec (~> 3.7)
......
...@@ -11,9 +11,15 @@ module QA ...@@ -11,9 +11,15 @@ module QA
autoload :Scenario, 'qa/runtime/scenario' autoload :Scenario, 'qa/runtime/scenario'
autoload :Browser, 'qa/runtime/browser' autoload :Browser, 'qa/runtime/browser'
autoload :Env, 'qa/runtime/env' autoload :Env, 'qa/runtime/env'
autoload :RSAKey, 'qa/runtime/rsa_key'
autoload :Address, 'qa/runtime/address' autoload :Address, 'qa/runtime/address'
autoload :API, 'qa/runtime/api' autoload :API, 'qa/runtime/api'
module Key
autoload :Base, 'qa/runtime/key/base'
autoload :RSA, 'qa/runtime/key/rsa'
autoload :ECDSA, 'qa/runtime/key/ecdsa'
autoload :ED25519, 'qa/runtime/key/ed25519'
end
end end
## ##
......
...@@ -2,7 +2,8 @@ module QA ...@@ -2,7 +2,8 @@ module QA
module Factory module Factory
module Repository module Repository
class Push < Factory::Base class Push < Factory::Base
attr_writer :file_name, :file_content, :commit_message, :branch_name, :new_branch attr_accessor :file_name, :file_content, :commit_message,
:branch_name, :new_branch, :remote_branch
dependency Factory::Resource::Project, as: :project do |project| dependency Factory::Resource::Project, as: :project do |project|
project.name = 'project-with-code' project.name = 'project-with-code'
...@@ -17,23 +18,32 @@ module QA ...@@ -17,23 +18,32 @@ module QA
@new_branch = true @new_branch = true
end end
def remote_branch
@remote_branch ||= branch_name
end
def fabricate! def fabricate!
project.visit! project.visit!
Git::Repository.perform do |repository| Git::Repository.perform do |repository|
repository.location = Page::Project::Show.act do repository.uri = Page::Project::Show.act do
choose_repository_clone_http choose_repository_clone_http
repository_location repository_location.uri
end end
repository.use_default_credentials repository.use_default_credentials
repository.clone repository.clone
repository.configure_identity('GitLab QA', 'root@gitlab.com') repository.configure_identity('GitLab QA', 'root@gitlab.com')
repository.checkout(@branch_name) unless @new_branch if new_branch
repository.add_file(@file_name, @file_content) repository.checkout_new_branch(branch_name)
repository.commit(@commit_message) else
repository.push_changes(@branch_name) repository.checkout(branch_name)
end
repository.add_file(file_name, file_content)
repository.commit(commit_message)
repository.push_changes("#{branch_name}:#{remote_branch}")
end end
end end
end end
......
...@@ -39,7 +39,9 @@ module QA ...@@ -39,7 +39,9 @@ module QA
resource.project = project resource.project = project
resource.file_name = 'README.md' resource.file_name = 'README.md'
resource.commit_message = 'Add readme' resource.commit_message = 'Add readme'
resource.branch_name = "master:#{@branch_name}" resource.branch_name = 'master'
resource.new_branch = false
resource.remote_branch = @branch_name
end end
Page::Project::Show.act { wait_for_push } Page::Project::Show.act { wait_for_push }
......
...@@ -4,15 +4,15 @@ module QA ...@@ -4,15 +4,15 @@ module QA
class DeployKey < Factory::Base class DeployKey < Factory::Base
attr_accessor :title, :key attr_accessor :title, :key
product :title do product :fingerprint do |resource|
Page::Project::Settings::Repository.act do Page::Project::Settings::Repository.act do
expand_deploy_keys(&:key_title) expand_deploy_keys do |key|
end key_offset = key.key_titles.index do |title|
end title.text == resource.title
end
product :fingerprint do key.key_fingerprints[key_offset].text
Page::Project::Settings::Repository.act do end
expand_deploy_keys(&:key_fingerprint)
end end
end end
......
...@@ -24,12 +24,14 @@ module QA ...@@ -24,12 +24,14 @@ module QA
dependency Factory::Repository::Push, as: :target do |push, factory| dependency Factory::Repository::Push, as: :target do |push, factory|
factory.project.visit! factory.project.visit!
push.project = factory.project push.project = factory.project
push.branch_name = "master:#{factory.target_branch}" push.branch_name = 'master'
push.remote_branch = factory.target_branch
end end
dependency Factory::Repository::Push, as: :source do |push, factory| dependency Factory::Repository::Push, as: :source do |push, factory|
push.project = factory.project push.project = factory.project
push.branch_name = "#{factory.target_branch}:#{factory.source_branch}" push.branch_name = factory.target_branch
push.remote_branch = factory.source_branch
push.file_name = "added_file.txt" push.file_name = "added_file.txt"
push.file_content = "File Added" push.file_content = "File Added"
end end
......
...@@ -17,6 +17,13 @@ module QA ...@@ -17,6 +17,13 @@ module QA
Page::Project::Show.act { project_name } Page::Project::Show.act { project_name }
end end
product :repository_ssh_location do
Page::Project::Show.act do
choose_repository_clone_ssh
repository_location
end
end
def fabricate! def fabricate!
group.visit! group.visit!
......
...@@ -16,8 +16,7 @@ module QA ...@@ -16,8 +16,7 @@ module QA
Page::Project::Settings::CICD.perform do |setting| Page::Project::Settings::CICD.perform do |setting|
setting.expand_secret_variables do |page| setting.expand_secret_variables do |page|
page.fill_variable_key(key) page.fill_variable(key, value)
page.fill_variable_value(value)
page.save_variables page.save_variables
end end
......
...@@ -14,7 +14,7 @@ module QA ...@@ -14,7 +14,7 @@ module QA
def initialize(git_uri) def initialize(git_uri)
@git_uri = git_uri @git_uri = git_uri
@uri = @uri =
if git_uri.start_with?('ssh://') if git_uri =~ %r{\A(?:ssh|http|https)://}
URI.parse(git_uri) URI.parse(git_uri)
else else
*rest, path = git_uri.split(':') *rest, path = git_uri.split(':')
......
...@@ -15,8 +15,7 @@ module QA ...@@ -15,8 +15,7 @@ module QA
end end
end end
def location=(address) def uri=(address)
@location = address
@uri = URI(address) @uri = URI(address)
end end
...@@ -43,6 +42,10 @@ module QA ...@@ -43,6 +42,10 @@ module QA
`git checkout "#{branch_name}"` `git checkout "#{branch_name}"`
end end
def checkout_new_branch(branch_name)
`git checkout -b "#{branch_name}"`
end
def shallow_clone def shallow_clone
clone('--depth 1') clone('--depth 1')
end end
......
...@@ -64,6 +64,10 @@ module QA ...@@ -64,6 +64,10 @@ module QA
find(element_selector_css(name)) find(element_selector_css(name))
end end
def all_elements(name)
all(element_selector_css(name))
end
def click_element(name) def click_element(name)
find_element(name).click find_element(name).click
end end
......
...@@ -42,6 +42,18 @@ module QA ...@@ -42,6 +42,18 @@ module QA
end end
end end
def key_titles
within_project_deploy_keys do
all_elements(:key_title)
end
end
def key_fingerprints
within_project_deploy_keys do
all_elements(:key_fingerprint)
end
end
private private
def within_project_deploy_keys def within_project_deploy_keys
......
...@@ -7,10 +7,8 @@ module QA ...@@ -7,10 +7,8 @@ module QA
view 'app/views/ci/variables/_variable_row.html.haml' do view 'app/views/ci/variables/_variable_row.html.haml' do
element :variable_row, '.ci-variable-row-body' element :variable_row, '.ci-variable-row-body'
element :variable_key, '.js-ci-variable-input-key' element :variable_key, '.qa-ci-variable-input-key'
element :variable_value, '.js-ci-variable-input-value' element :variable_value, '.qa-ci-variable-input-value'
element :key_placeholder, 'Input variable key'
element :value_placeholder, 'Input variable value'
end end
view 'app/views/ci/variables/_index.html.haml' do view 'app/views/ci/variables/_index.html.haml' do
...@@ -18,12 +16,14 @@ module QA ...@@ -18,12 +16,14 @@ module QA
element :reveal_values, '.js-secret-value-reveal-button' element :reveal_values, '.js-secret-value-reveal-button'
end end
def fill_variable_key(key) def fill_variable(key, value)
fill_in('Input variable key', with: key, match: :first) keys = all_elements(:ci_variable_input_key)
end index = keys.size - 1
def fill_variable_value(value) # After we fill the key, JS would generate another field so
fill_in('Input variable value', with: value, match: :first) # we need to use the same index to find the corresponding one.
keys[index].set(key)
all_elements(:ci_variable_input_value)[index].set(value)
end end
def save_variables def save_variables
...@@ -36,7 +36,7 @@ module QA ...@@ -36,7 +36,7 @@ module QA
def variable_value(key) def variable_value(key)
within('.ci-variable-row-body', text: key) do within('.ci-variable-row-body', text: key) do
find('.js-ci-variable-input-value').value find('.qa-ci-variable-input-value').value
end end
end end
end end
......
...@@ -38,11 +38,7 @@ module QA ...@@ -38,11 +38,7 @@ module QA
end end
def repository_location def repository_location
find('#project_clone').value Git::Location.new(find('#project_clone').value)
end
def repository_location_uri
Git::Location.new(repository_location)
end end
def project_name def project_name
...@@ -91,7 +87,7 @@ module QA ...@@ -91,7 +87,7 @@ module QA
end end
# Ensure git clone textbox was updated # Ensure git clone textbox was updated
repository_location.include?(detect_text) repository_location.git_uri.include?(detect_text)
end end
end end
end end
......
module QA
module Runtime
module Key
class Base
attr_reader :name, :bits, :private_key, :public_key, :fingerprint
def initialize(name, bits)
@name = name
@bits = bits
Dir.mktmpdir do |dir|
path = "#{dir}/id_#{name}"
ssh_keygen(name, bits, path)
populate_key_data(path)
end
end
private
def ssh_keygen(name, bits, path)
cmd = %W[ssh-keygen -t #{name} -b #{bits} -f #{path} -N] << ''
Service::Shellout.shell(cmd)
end
def populate_key_data(path)
@private_key = File.binread(path)
@public_key = File.binread("#{path}.pub")
@fingerprint =
`ssh-keygen -l -E md5 -f #{path} | cut -d' ' -f2 | cut -d: -f2-`.chomp
end
end
end
end
end
module QA
module Runtime
module Key
class ECDSA < Base
def initialize(bits = 521)
super('ecdsa', bits)
end
end
end
end
end
module QA
module Runtime
module Key
class ED25519 < Base
def initialize
super('ed25519', 256)
end
end
end
end
end
module QA
module Runtime
module Key
class RSA < Base
def initialize(bits = 4096)
super('rsa', bits)
end
end
end
end
end
require 'net/ssh'
require 'forwardable'
module QA
module Runtime
class RSAKey
extend Forwardable
attr_reader :key
def_delegators :@key, :fingerprint, :to_pem
def initialize(bits = 4096)
@key = OpenSSL::PKey::RSA.new(bits)
end
def public_key
@public_key ||= "#{key.ssh_type} #{[key.to_blob].pack('m0')}"
end
end
end
end
...@@ -5,6 +5,8 @@ module QA ...@@ -5,6 +5,8 @@ module QA
module Shellout module Shellout
CommandError = Class.new(StandardError) CommandError = Class.new(StandardError)
module_function
## ##
# TODO, make it possible to use generic QA framework classes # TODO, make it possible to use generic QA framework classes
# as a library - gitlab-org/gitlab-qa#94 # as a library - gitlab-org/gitlab-qa#94
...@@ -12,7 +14,7 @@ module QA ...@@ -12,7 +14,7 @@ module QA
def shell(command) def shell(command)
puts "Executing `#{command}`" puts "Executing `#{command}`"
Open3.popen2e(command) do |_in, out, wait| Open3.popen2e(*command) do |_in, out, wait|
out.each { |line| puts line } out.each { |line| puts line }
if wait.value.exited? && wait.value.exitstatus.nonzero? if wait.value.exited? && wait.value.exitstatus.nonzero?
......
...@@ -4,7 +4,7 @@ module QA ...@@ -4,7 +4,7 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login) Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials } Page::Main::Login.act { sign_in_using_credentials }
key = Runtime::RSAKey.new key = Runtime::Key::RSA.new
deploy_key_title = 'deploy key title' deploy_key_title = 'deploy key title'
deploy_key_value = key.public_key deploy_key_value = key.public_key
...@@ -13,7 +13,6 @@ module QA ...@@ -13,7 +13,6 @@ module QA
resource.key = deploy_key_value resource.key = deploy_key_value
end end
expect(deploy_key.title).to eq(deploy_key_title)
expect(deploy_key.fingerprint).to eq(key.fingerprint) expect(deploy_key.fingerprint).to eq(key.fingerprint)
end end
end end
......
...@@ -2,79 +2,103 @@ require 'digest/sha1' ...@@ -2,79 +2,103 @@ require 'digest/sha1'
module QA module QA
feature 'cloning code using a deploy key', :core, :docker do feature 'cloning code using a deploy key', :core, :docker do
let(:runner_name) { "qa-runner-#{Time.now.to_i}" } def login
let(:key) { Runtime::RSAKey.new } Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
end
given(:project) do before(:all) do
Factory::Resource::Project.fabricate! do |resource| login
@runner_name = "qa-runner-#{Time.now.to_i}"
@project = Factory::Resource::Project.fabricate! do |resource|
resource.name = 'deploy-key-clone-project' resource.name = 'deploy-key-clone-project'
end end
end
after do @repository_location = @project.repository_ssh_location
Service::Runner.new(runner_name).remove!
end
scenario 'user sets up a deploy key to clone code using pipelines' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Resource::Runner.fabricate! do |resource| Factory::Resource::Runner.fabricate! do |resource|
resource.project = project resource.project = @project
resource.name = runner_name resource.name = @runner_name
resource.tags = %w[qa docker] resource.tags = %w[qa docker]
resource.image = 'gitlab/gitlab-runner:ubuntu' resource.image = 'gitlab/gitlab-runner:ubuntu'
end end
Factory::Resource::DeployKey.fabricate! do |resource| Page::Menu::Main.act { sign_out }
resource.project = project end
resource.title = 'deploy key title'
resource.key = key.public_key
end
Factory::Resource::SecretVariable.fabricate! do |resource| after(:all) do
resource.project = project Service::Runner.new(@runner_name).remove!
resource.key = 'DEPLOY_KEY' end
resource.value = key.to_pem
end
project.visit! keys = [
Runtime::Key::RSA.new(8192),
Runtime::Key::ECDSA.new(521),
Runtime::Key::ED25519.new
]
repository_uri = Page::Project::Show.act do keys.each do |key|
choose_repository_clone_ssh scenario "user sets up a deploy key with #{key.name}(#{key.bits}) to clone code using pipelines" do
repository_location_uri login
end
gitlab_ci = <<~YAML Factory::Resource::DeployKey.fabricate! do |resource|
cat-config: resource.project = @project
script: resource.title = "deploy key #{key.name}(#{key.bits})"
- mkdir -p ~/.ssh resource.key = key.public_key
- ssh-keyscan -p #{repository_uri.port} #{repository_uri.host} >> ~/.ssh/known_hosts end
- eval $(ssh-agent -s)
- echo "$DEPLOY_KEY" | ssh-add - deploy_key_name = "DEPLOY_KEY_#{key.name}_#{key.bits}"
- git clone #{repository_uri.git_uri}
- sha1sum #{project.name}/.gitlab-ci.yml Factory::Resource::SecretVariable.fabricate! do |resource|
tags: resource.project = @project
- qa resource.key = deploy_key_name
- docker resource.value = key.private_key
YAML end
Factory::Repository::Push.fabricate! do |resource| gitlab_ci = <<~YAML
resource.project = project cat-config:
resource.file_name = '.gitlab-ci.yml' script:
resource.commit_message = 'Add .gitlab-ci.yml' - mkdir -p ~/.ssh
resource.file_content = gitlab_ci - ssh-keyscan -p #{@repository_location.port} #{@repository_location.host} >> ~/.ssh/known_hosts
end - eval $(ssh-agent -s)
- ssh-add -D
- echo "$#{deploy_key_name}" | ssh-add -
- git clone #{@repository_location.git_uri}
- cd #{@project.name}
- git checkout #{deploy_key_name}
- sha1sum .gitlab-ci.yml
tags:
- qa
- docker
YAML
Factory::Repository::Push.fabricate! do |resource|
resource.project = @project
resource.file_name = '.gitlab-ci.yml'
resource.commit_message = 'Add .gitlab-ci.yml'
resource.file_content = gitlab_ci
resource.branch_name = deploy_key_name
resource.new_branch = true
end
sha1sum = Digest::SHA1.hexdigest(gitlab_ci)
Page::Project::Show.act { wait_for_push }
Page::Menu::Side.act { click_ci_cd_pipelines }
Page::Project::Pipeline::Index.act { go_to_latest_pipeline }
sha1sum = Digest::SHA1.hexdigest(gitlab_ci) Page::Project::Pipeline::Show.act do
go_to_first_job
Page::Project::Show.act { wait_for_push } wait do
Page::Menu::Side.act { click_ci_cd_pipelines } !has_content?('running')
Page::Project::Pipeline::Index.act { go_to_latest_pipeline } end
Page::Project::Pipeline::Show.act { go_to_first_job } end
Page::Project::Job::Show.perform do |job| Page::Project::Job::Show.perform do |job|
expect(job.output).to include(sha1sum) expect(job.output).to include(sha1sum)
end
end end
end end
end end
......
...@@ -18,7 +18,7 @@ module QA ...@@ -18,7 +18,7 @@ module QA
end end
Git::Repository.perform do |repository| Git::Repository.perform do |repository|
repository.location = location repository.uri = location.uri
repository.use_default_credentials repository.use_default_credentials
repository.act do repository.act do
...@@ -33,7 +33,7 @@ module QA ...@@ -33,7 +33,7 @@ module QA
scenario 'user performs a deep clone' do scenario 'user performs a deep clone' do
Git::Repository.perform do |repository| Git::Repository.perform do |repository|
repository.location = location repository.uri = location.uri
repository.use_default_credentials repository.use_default_credentials
repository.act { clone } repository.act { clone }
...@@ -44,7 +44,7 @@ module QA ...@@ -44,7 +44,7 @@ module QA
scenario 'user performs a shallow clone' do scenario 'user performs a shallow clone' do
Git::Repository.perform do |repository| Git::Repository.perform do |repository|
repository.location = location repository.uri = location.uri
repository.use_default_credentials repository.use_default_credentials
repository.act { shallow_clone } repository.act { shallow_clone }
......
...@@ -42,7 +42,7 @@ module QA ...@@ -42,7 +42,7 @@ module QA
project.visit! project.visit!
Git::Repository.perform do |repository| Git::Repository.perform do |repository|
repository.location = location repository.uri = location.uri
repository.use_default_credentials repository.use_default_credentials
repository.act do repository.act do
......
describe QA::Runtime::Key::ECDSA do
describe '#public_key' do
[256, 384, 521].each do |bits|
it "generates a public #{bits}-bits ECDSA key" do
subject = described_class.new(bits).public_key
expect(subject).to match(%r{\Aecdsa\-sha2\-\w+ AAAA[0-9A-Za-z+/]+={0,3}})
end
end
end
describe '#new' do
it 'does not support arbitrary bits' do
expect { described_class.new(123) }
.to raise_error(QA::Service::Shellout::CommandError)
end
end
end
describe QA::Runtime::Key::ED25519 do
describe '#public_key' do
subject { described_class.new.public_key }
it 'generates a public ED25519 key' do
expect(subject).to match(%r{\Assh\-ed25519 AAAA[0-9A-Za-z+/]})
end
end
end
describe QA::Runtime::RSAKey do describe QA::Runtime::Key::RSA do
describe '#public_key' do describe '#public_key' do
subject { described_class.new.public_key } subject { described_class.new.public_key }
it 'generates a public RSA key' do it 'generates a public RSA key' do
expect(subject).to match(%r{\Assh\-rsa AAAA[0-9A-Za-z+/]+={0,3}\z}) expect(subject).to match(%r{\Assh\-rsa AAAA[0-9A-Za-z+/]+={0,3}})
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment