diff options
59 files changed, 833 insertions, 94 deletions
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue index 32491dfbcb6..5d7be0c705a 100644 --- a/app/assets/javascripts/boards/components/boards_selector.vue +++ b/app/assets/javascripts/boards/components/boards_selector.vue @@ -315,8 +315,7 @@ export default { <gl-dropdown-item v-if="showDelete" - class="text-danger" - data-qa-selector="delete_board_button" + class="text-danger js-delete-board" @click.prevent="showPage('delete')" > {{ s__('IssueBoards|Delete board') }} diff --git a/app/finders/keys_finder.rb b/app/finders/keys_finder.rb new file mode 100644 index 00000000000..d6ba7cb290d --- /dev/null +++ b/app/finders/keys_finder.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true +class KeysFinder + InvalidFingerprint = Class.new(StandardError) + GitLabAccessDeniedError = Class.new(StandardError) + + FINGERPRINT_ATTRIBUTES = { + 'sha256' => 'fingerprint_sha256', + 'md5' => 'fingerprint' + }.freeze + + def initialize(current_user, params) + @current_user = current_user + @params = params + end + + def execute + raise GitLabAccessDeniedError unless current_user.admin? + raise InvalidFingerprint unless valid_fingerprint_param? + + Key.where(fingerprint_query).first # rubocop: disable CodeReuse/ActiveRecord + end + + private + + attr_reader :current_user, :params + + def valid_fingerprint_param? + if fingerprint_type == "sha256" + Base64.decode64(fingerprint).length == 32 + else + fingerprint =~ /^(\h{2}:){15}\h{2}/ + end + end + + def fingerprint_query + fingerprint_attribute = FINGERPRINT_ATTRIBUTES[fingerprint_type] + + Key.arel_table[fingerprint_attribute].eq(fingerprint) + end + + def fingerprint_type + if params[:fingerprint].start_with?(/sha256:|SHA256:/) + "sha256" + else + "md5" + end + end + + def fingerprint + if fingerprint_type == "sha256" + params[:fingerprint].gsub(/sha256:|SHA256:/, "") + else + params[:fingerprint] + end + end +end diff --git a/app/models/concerns/sha256_attribute.rb b/app/models/concerns/sha256_attribute.rb new file mode 100644 index 00000000000..1bd1ad177a2 --- /dev/null +++ b/app/models/concerns/sha256_attribute.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Sha256Attribute + extend ActiveSupport::Concern + + class_methods do + def sha256_attribute(name) + return if ENV['STATIC_VERIFICATION'] + + validate_binary_column_exists!(name) unless Rails.env.production? + + attribute(name, Gitlab::Database::Sha256Attribute.new) + end + + # This only gets executed in non-production environments as an additional check to ensure + # the column is the correct type. In production it should behave like any other attribute. + # See https://gitlab.com/gitlab-org/gitlab/merge_requests/5502 for more discussion + def validate_binary_column_exists!(name) + return unless database_exists? + + unless table_exists? + warn "WARNING: sha256_attribute #{name.inspect} is invalid since the table doesn't exist - you may need to run database migrations" + return + end + + column = columns.find { |c| c.name == name.to_s } + + unless column + warn "WARNING: sha256_attribute #{name.inspect} is invalid since the column doesn't exist - you may need to run database migrations" + return + end + + unless column.type == :binary + raise ArgumentError.new("sha256_attribute #{name.inspect} is invalid since the column type is not :binary") + end + rescue => error + Gitlab::AppLogger.error "Sha256Attribute initialization: #{error.message}" + raise + end + + def database_exists? + ApplicationRecord.connection + + true + rescue + false + end + end +end diff --git a/app/models/key.rb b/app/models/key.rb index ff601966c26..f66aa4fb329 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -5,6 +5,9 @@ require 'digest/md5' class Key < ApplicationRecord include AfterCommitQueue include Sortable + include Sha256Attribute + + sha256_attribute :fingerprint_sha256 belongs_to :user @@ -34,6 +37,8 @@ class Key < ApplicationRecord after_destroy :post_destroy_hook after_destroy :refresh_user_cache + alias_attribute :fingerprint_md5, :fingerprint + def self.regular_keys where(type: ['Key', nil]) end @@ -114,10 +119,12 @@ class Key < ApplicationRecord def generate_fingerprint self.fingerprint = nil + self.fingerprint_sha256 = nil return unless public_key.valid? - self.fingerprint = public_key.fingerprint + self.fingerprint_md5 = public_key.fingerprint + self.fingerprint_sha256 = public_key.fingerprint("SHA256").gsub("SHA256:", "") end def key_meets_restrictions diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb index d935d9e8cdc..a49983a84fc 100644 --- a/app/services/git/base_hooks_service.rb +++ b/app/services/git/base_hooks_service.rb @@ -163,7 +163,7 @@ module Git end def logger - if Sidekiq.server? + if Gitlab::Runtime.sidekiq? Sidekiq.logger else # This service runs in Sidekiq, so this shouldn't ever be diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml index 0ef01dec493..02f1a267044 100644 --- a/app/views/profiles/keys/_key_details.html.haml +++ b/app/views/profiles/keys/_key_details.html.haml @@ -17,11 +17,21 @@ .col-md-8 = form_errors(@key, type: 'key') unless @key.valid? - %p - %span.light= _('Fingerprint:') - %code.key-fingerprint= @key.fingerprint %pre.well-pre = @key.key + .card + .card-header + = _('Fingerprints') + %ul.content-list + %li + %span.light= 'MD5:' + %code.key-fingerprint= @key.fingerprint + - if @key.fingerprint_sha256.present? + %li + %span.light= 'SHA256:' + %code.key-fingerprint= @key.fingerprint_sha256 + + .col-md-12 .float-right - if @key.can_delete? diff --git a/changelogs/unreleased/feat-ssh-sha256.yml b/changelogs/unreleased/feat-ssh-sha256.yml new file mode 100644 index 00000000000..91a881a5962 --- /dev/null +++ b/changelogs/unreleased/feat-ssh-sha256.yml @@ -0,0 +1,5 @@ +--- +title: add sha256 fingerprint to keys model, view and extend users API to search user via fingerprint +merge_request: 19860 +author: Roger Meier +type: added diff --git a/config/application.rb b/config/application.rb index cad5c8bbe76..28c1eba920b 100644 --- a/config/application.rb +++ b/config/application.rb @@ -22,6 +22,7 @@ module Gitlab require_dependency Rails.root.join('lib/gitlab/current_settings') require_dependency Rails.root.join('lib/gitlab/middleware/read_only') require_dependency Rails.root.join('lib/gitlab/middleware/basic_health_check') + require_dependency Rails.root.join('lib/gitlab/runtime') # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers @@ -255,7 +256,7 @@ module Gitlab caching_config_hash[:compress] = false caching_config_hash[:namespace] = Gitlab::Redis::Cache::CACHE_NAMESPACE caching_config_hash[:expires_in] = 2.weeks # Cache should not grow forever - if Sidekiq.server? || defined?(::Puma) # threaded context + if Gitlab::Runtime.multi_threaded? caching_config_hash[:pool_size] = Gitlab::Redis::Cache.pool_size caching_config_hash[:pool_timeout] = 1 end diff --git a/config/environments/development.rb b/config/environments/development.rb index 2939e13ef94..dc804197fef 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -46,7 +46,7 @@ Rails.application.configure do # Do not log asset requests config.assets.quiet = true - config.allow_concurrency = defined?(::Puma) + config.allow_concurrency = Gitlab::Runtime.multi_threaded? # BetterErrors live shell (REPL) on every stack frame BetterErrors::Middleware.allow_ip!("127.0.0.1/0") diff --git a/config/environments/production.rb b/config/environments/production.rb index 09bcf49a9a5..7ec18547b2f 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -75,5 +75,5 @@ Rails.application.configure do config.eager_load = true - config.allow_concurrency = defined?(::Puma) + config.allow_concurrency = Gitlab::Runtime.multi_threaded? end diff --git a/config/initializers/0_runtime_identify.rb b/config/initializers/0_runtime_identify.rb new file mode 100644 index 00000000000..2b5d08102eb --- /dev/null +++ b/config/initializers/0_runtime_identify.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +begin + Gitlab::AppLogger.info("Runtime: #{Gitlab::Runtime.name}") +rescue => e + message = <<-NOTICE + \n!! RUNTIME IDENTIFICATION FAILED: #{e} + Runtime based configuration settings may not work properly. + If you continue to see this error, please file an issue via + https://gitlab.com/gitlab-org/gitlab/issues/new + NOTICE + Gitlab::AppLogger.error(message) +end diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 8e4aa5701b4..bb0c4696eff 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -364,7 +364,7 @@ Gitlab.ee do # To ensure acceptable performance we only allow feature to be used with # multithreaded web-server Puma. This will be removed once download logic is moved # to GitLab workhorse - Settings.dependency_proxy['enabled'] = false unless defined?(::Puma) + Settings.dependency_proxy['enabled'] = false unless Gitlab::Runtime.puma? end # diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb index d40049970c1..513f4d9e6ad 100644 --- a/config/initializers/7_prometheus_metrics.rb +++ b/config/initializers/7_prometheus_metrics.rb @@ -4,11 +4,11 @@ require 'prometheus/client' def prometheus_default_multiproc_dir return unless Rails.env.development? || Rails.env.test? - if Sidekiq.server? + if Gitlab::Runtime.sidekiq? Rails.root.join('tmp/prometheus_multiproc_dir/sidekiq') - elsif defined?(Unicorn::Worker) + elsif Gitlab::Runtime.unicorn? Rails.root.join('tmp/prometheus_multiproc_dir/unicorn') - elsif defined?(::Puma) + elsif Gitlab::Runtime.puma? Rails.root.join('tmp/prometheus_multiproc_dir/puma') else Rails.root.join('tmp/prometheus_multiproc_dir') @@ -55,9 +55,9 @@ if !Rails.env.test? && Gitlab::Metrics.prometheus_metrics_enabled? Gitlab::Cluster::LifecycleEvents.on_master_start do ::Prometheus::Client.reinitialize_on_pid_change(force: true) - if defined?(::Unicorn) + if Gitlab::Runtime.unicorn? Gitlab::Metrics::Samplers::UnicornSampler.instance(Settings.monitoring.unicorn_sampler_interval).start - elsif defined?(::Puma) + elsif Gitlab::Runtime.puma? Gitlab::Metrics::Samplers::PumaSampler.instance(Settings.monitoring.puma_sampler_interval).start end @@ -65,7 +65,7 @@ if !Rails.env.test? && Gitlab::Metrics.prometheus_metrics_enabled? end end -if defined?(::Unicorn) || defined?(::Puma) +if Gitlab::Runtime.app_server? Gitlab::Cluster::LifecycleEvents.on_master_start do Gitlab::Metrics::Exporter::WebExporter.instance.start end diff --git a/config/initializers/active_record_lifecycle.rb b/config/initializers/active_record_lifecycle.rb index 61f1d299960..2cf0f0439a9 100644 --- a/config/initializers/active_record_lifecycle.rb +++ b/config/initializers/active_record_lifecycle.rb @@ -2,7 +2,7 @@ # Don't handle sidekiq configuration as it # has its own special active record configuration here -if defined?(ActiveRecord::Base) && !Sidekiq.server? +if defined?(ActiveRecord::Base) && !Gitlab::Runtime.sidekiq? Gitlab::Cluster::LifecycleEvents.on_worker_start do ActiveSupport.on_load(:active_record) do ActiveRecord::Base.establish_connection diff --git a/config/initializers/cluster_events_before_phased_restart.rb b/config/initializers/cluster_events_before_phased_restart.rb index cbb1dd1a53a..aae5470d6ae 100644 --- a/config/initializers/cluster_events_before_phased_restart.rb +++ b/config/initializers/cluster_events_before_phased_restart.rb @@ -5,10 +5,8 @@ # # Follow-up the issue: https://gitlab.com/gitlab-org/gitlab/issues/34107 -if defined?(::Puma) +if Gitlab::Runtime.puma? Puma::Cluster.prepend(::Gitlab::Cluster::Mixins::PumaCluster) -end - -if defined?(::Unicorn::HttpServer) +elsif Gitlab::Runtime.unicorn? Unicorn::HttpServer.prepend(::Gitlab::Cluster::Mixins::UnicornHttpServer) end diff --git a/config/initializers/database_config.rb b/config/initializers/database_config.rb index d8c2821066b..509f04c9b02 100644 --- a/config/initializers/database_config.rb +++ b/config/initializers/database_config.rb @@ -2,7 +2,7 @@ # when running on puma, scale connection pool size with the number # of threads per worker process -if defined?(::Puma) +if Gitlab::Runtime.puma? db_config = Gitlab::Database.config || Rails.application.config.database_configuration[Rails.env] puma_options = Puma.cli_config.options diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb index a8207862739..0acbe6a9258 100644 --- a/config/initializers/lograge.rb +++ b/config/initializers/lograge.rb @@ -1,5 +1,5 @@ # Only use Lograge for Rails -unless Sidekiq.server? +unless Gitlab::Runtime.sidekiq? filename = File.join(Rails.root, 'log', "#{Rails.env}_json.log") Rails.application.configure do diff --git a/config/initializers/rack_timeout.rb b/config/initializers/rack_timeout.rb index 246cf3482a4..1f1264de208 100644 --- a/config/initializers/rack_timeout.rb +++ b/config/initializers/rack_timeout.rb @@ -9,7 +9,7 @@ # and it's used only as the last resort. In such case this termination is # logged and we should fix the potential timeout issue in the code itself. -if defined?(::Puma) && !Rails.env.test? +if Gitlab::Runtime.puma? && !Rails.env.test? require 'rack/timeout/base' Gitlab::Application.configure do |config| diff --git a/config/initializers/tracing.rb b/config/initializers/tracing.rb index 5b55a06692e..0ae57021fcf 100644 --- a/config/initializers/tracing.rb +++ b/config/initializers/tracing.rb @@ -13,7 +13,7 @@ if Labkit::Tracing.enabled? end # Instrument Sidekiq server calls when running Sidekiq server - if Sidekiq.server? + if Gitlab::Runtime.sidekiq? Sidekiq.configure_server do |config| config.server_middleware do |chain| chain.add Labkit::Tracing::Sidekiq::ServerMiddleware diff --git a/config/initializers/validate_puma.rb b/config/initializers/validate_puma.rb index 64bd6e7bbc1..5abcfbfe6be 100644 --- a/config/initializers/validate_puma.rb +++ b/config/initializers/validate_puma.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -if defined?(::Puma) && ::Puma.cli_config.options[:workers].to_i.zero? +if Gitlab::Runtime.puma? && ::Puma.cli_config.options[:workers].to_i.zero? raise 'Puma is only supported in Cluster-mode: workers > 0' end diff --git a/db/migrate/20191208071111_add_fingerprint_sha256_to_key.rb b/db/migrate/20191208071111_add_fingerprint_sha256_to_key.rb new file mode 100644 index 00000000000..1bc87357f7d --- /dev/null +++ b/db/migrate/20191208071111_add_fingerprint_sha256_to_key.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AddFingerprintSha256ToKey < ActiveRecord::Migration[5.0] + DOWNTIME = false + + def up + add_column(:keys, :fingerprint_sha256, :binary) + end + + def down + remove_column(:keys, :fingerprint_sha256) if column_exists?(:keys, :fingerprint_sha256) + end +end diff --git a/db/migrate/20191208071112_add_fingerprint_sha256_index_to_key.rb b/db/migrate/20191208071112_add_fingerprint_sha256_index_to_key.rb new file mode 100644 index 00000000000..6f0c4bcd321 --- /dev/null +++ b/db/migrate/20191208071112_add_fingerprint_sha256_index_to_key.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddFingerprintSha256IndexToKey < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index(:keys, "fingerprint_sha256") + end + + def down + remove_concurrent_index(:keys, "fingerprint_sha256") + end +end diff --git a/db/schema.rb b/db/schema.rb index deebbcb430d..c677944bad6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_12_06_122926) do +ActiveRecord::Schema.define(version: 2019_12_08_071112) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" @@ -2206,7 +2206,9 @@ ActiveRecord::Schema.define(version: 2019_12_06_122926) do t.string "fingerprint" t.boolean "public", default: false, null: false t.datetime "last_used_at" + t.binary "fingerprint_sha256" t.index ["fingerprint"], name: "index_keys_on_fingerprint", unique: true + t.index ["fingerprint_sha256"], name: "index_keys_on_fingerprint_sha256" t.index ["id", "type"], name: "index_on_deploy_keys_id_and_type_and_public", unique: true, where: "(public = true)" t.index ["user_id"], name: "index_keys_on_user_id" end diff --git a/doc/api/keys.md b/doc/api/keys.md index 06b31a67d6a..5dedb630a27 100644 --- a/doc/api/keys.md +++ b/doc/api/keys.md @@ -4,13 +4,19 @@ Get SSH key with user by ID of an SSH key. Note only administrators can lookup SSH key with user by ID of an SSH key. -``` +```text GET /keys/:id ``` -Parameters: +| Attribute | Type | Required | Description | +|:----------|:--------|:---------|:---------------------| +| `id` | integer | yes | The ID of an SSH key | + +Example request: -- `id` (required) - The ID of an SSH key +```sh +curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/keys/1 +``` ```json { @@ -51,3 +57,74 @@ Parameters: } } ``` + +## Get user by fingerprint of SSH key + +You can search for a user that owns a specific SSH key. Note only administrators can lookup SSH key with the fingerprint of an SSH key. + +```text +GET /keys +``` + +| Attribute | Type | Required | Description | +|:--------------|:-------|:---------|:------------------------------| +| `fingerprint` | string | yes | The fingerprint of an SSH key | + +Example request: + +```sh +curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/keys?fingerprint=ba:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d1' +``` + +If using sha256 fingerprint API calls, make sure that the fingerprint is URL-encoded. + +For example, `/` is represented by `%2F` and `:` is represented by`%3A`: + +```sh +curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/keys?fingerprint=SHA256%3AnUhzNyftwADy8AH3wFY31tAKs7HufskYTte2aXo%2FlCg +``` + +Example response: + +```json +{ + "id": 1, + "title": "Sample key 1", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt1016k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "created_at": "2019-11-14T15:11:13.222Z", + "user": { + "id": 1, + "name": "Administrator", + "username": "root", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "http://0.0.0.0:3000/root", + "created_at": "2019-11-14T15:09:34.831Z", + "bio": null, + "location": null, + "public_email": "", + "skype": "", + "linkedin": "", + "twitter": "", + "website_url": "", + "organization": null, + "last_sign_in_at": "2019-11-16T22:41:26.663Z", + "confirmed_at": "2019-11-14T15:09:34.575Z", + "last_activity_on": "2019-11-20", + "email": "admin@example.com", + "theme_id": 1, + "color_scheme_id": 1, + "projects_limit": 100000, + "current_sign_in_at": "2019-11-19T14:42:18.078Z", + "identities": [ + ], + "can_create_group": true, + "can_create_project": true, + "two_factor_enabled": false, + "external": false, + "private_profile": false, + "shared_runners_minutes_limit": null, + "extra_shared_runners_minutes_limit": null + } +} +``` diff --git a/lib/api/keys.rb b/lib/api/keys.rb index d5280a0035d..8f2fd8cbae2 100644 --- a/lib/api/keys.rb +++ b/lib/api/keys.rb @@ -16,6 +16,23 @@ module API present key, with: Entities::SSHKeyWithUser, current_user: current_user end + + desc 'Get SSH Key information' do + success Entities::UserWithAdmin + end + params do + requires :fingerprint, type: String, desc: 'Search for a SSH fingerprint' + end + get do + authenticated_with_full_private_access! + + key = KeysFinder.new(current_user, params).execute + + not_found!('Key') unless key + present key, with: Entities::SSHKeyWithUser, current_user: current_user + rescue KeysFinder::InvalidFingerprint + render_api_error!('Failed to return the key', 400) + end end end end diff --git a/lib/gitlab.rb b/lib/gitlab.rb index 0e6db54eb46..f2bff51df38 100644 --- a/lib/gitlab.rb +++ b/lib/gitlab.rb @@ -100,8 +100,8 @@ module Gitlab end def self.process_name - return 'sidekiq' if Sidekiq.server? - return 'console' if defined?(Rails::Console) + return 'sidekiq' if Gitlab::Runtime.sidekiq? + return 'console' if Gitlab::Runtime.console? return 'test' if Rails.env.test? 'web' diff --git a/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml index 9a3ecd1c34f..975cb3b7698 100644 --- a/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml @@ -1,5 +1,16 @@ -# Full project: https://gitlab.com/pages/hugo -image: dettmering/hugo-build +--- +# All available Hugo versions are listed here: +# https://gitlab.com/pages/hugo/container_registry +image: registry.gitlab.com/pages/hugo:latest + +variables: + GIT_SUBMODULE_STRATEGY: recursive + +test: + script: + - hugo + except: + - master pages: script: @@ -9,9 +20,3 @@ pages: - public only: - master - -test: - script: - - hugo - except: - - master diff --git a/lib/gitlab/cluster/lifecycle_events.rb b/lib/gitlab/cluster/lifecycle_events.rb index 2b3dc94fc5e..4ae75e0db0a 100644 --- a/lib/gitlab/cluster/lifecycle_events.rb +++ b/lib/gitlab/cluster/lifecycle_events.rb @@ -149,10 +149,10 @@ module Gitlab def in_clustered_environment? # Sidekiq doesn't fork - return false if Sidekiq.server? + return false if Gitlab::Runtime.sidekiq? # Unicorn always forks - return true if defined?(::Unicorn) + return true if Gitlab::Runtime.unicorn? # Puma sometimes forks return true if in_clustered_puma? @@ -162,7 +162,7 @@ module Gitlab end def in_clustered_puma? - return false unless defined?(::Puma) + return false unless Gitlab::Runtime.puma? @puma_options && @puma_options[:workers] && @puma_options[:workers] > 0 end diff --git a/lib/gitlab/database/sha256_attribute.rb b/lib/gitlab/database/sha256_attribute.rb new file mode 100644 index 00000000000..adf3f7fb5a6 --- /dev/null +++ b/lib/gitlab/database/sha256_attribute.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Gitlab + module Database + # Class for casting binary data to hexadecimal SHA256 hashes (and vice-versa). + # + # Using Sha256Attribute allows you to store SHA256 values as binary while still + # using them as if they were stored as string values. This gives you the + # ease of use of string values, but without the storage overhead. + class Sha256Attribute < ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bytea + # Casts binary data to a SHA256 and remove trailing = and newline from encode64 + def deserialize(value) + value = super(value) + if value.present? + Base64.encode64(value).delete("=").chomp("\n") + else + nil + end + end + + # Casts a SHA256 in a proper binary format. which is 32 bytes long + def serialize(value) + arg = if value.present? + Base64.decode64(value) + else + nil + end + + super(arg) + end + end + end +end diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index 5b47853b9c1..373539f5516 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -29,7 +29,7 @@ module Gitlab PEM_REGEX = /\-+BEGIN CERTIFICATE\-+.+?\-+END CERTIFICATE\-+/m.freeze SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION' MAXIMUM_GITALY_CALLS = 30 - CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze + CLIENT_NAME = (Gitlab::Runtime.sidekiq? ? 'gitlab-sidekiq' : 'gitlab-web').freeze GITALY_METADATA_FILENAME = '.gitaly-metadata' MUTEX = Mutex.new @@ -383,17 +383,13 @@ module Gitlab end def self.long_timeout - if web_app_server? + if Gitlab::Runtime.app_server? default_timeout else 6.hours end end - def self.web_app_server? - defined?(::Unicorn) || defined?(::Puma) - end - def self.storage_metadata_file_path(storage) Gitlab::GitalyClient::StorageSettings.allow_disk_access do File.join( diff --git a/lib/gitlab/gpg.rb b/lib/gitlab/gpg.rb index 829e64b11a4..abe90bba19c 100644 --- a/lib/gitlab/gpg.rb +++ b/lib/gitlab/gpg.rb @@ -135,7 +135,7 @@ module Gitlab end def cleanup_time - Sidekiq.server? ? BG_CLEANUP_RUNTIME_S : FG_CLEANUP_RUNTIME_S + Gitlab::Runtime.sidekiq? ? BG_CLEANUP_RUNTIME_S : FG_CLEANUP_RUNTIME_S end def tmp_keychains_created diff --git a/lib/gitlab/health_checks/puma_check.rb b/lib/gitlab/health_checks/puma_check.rb index 7aafe29fbae..9f09070a57d 100644 --- a/lib/gitlab/health_checks/puma_check.rb +++ b/lib/gitlab/health_checks/puma_check.rb @@ -18,7 +18,7 @@ module Gitlab end def check - return unless defined?(::Puma) + return unless Gitlab::Runtime.puma? stats = Puma.stats stats = JSON.parse(stats) diff --git a/lib/gitlab/health_checks/unicorn_check.rb b/lib/gitlab/health_checks/unicorn_check.rb index a30ae015257..cdc6d2a7519 100644 --- a/lib/gitlab/health_checks/unicorn_check.rb +++ b/lib/gitlab/health_checks/unicorn_check.rb @@ -30,7 +30,7 @@ module Gitlab # to change so we can cache the list of servers. def http_servers strong_memoize(:http_servers) do - next unless defined?(::Unicorn::HttpServer) + next unless Gitlab::Runtime.unicorn? ObjectSpace.each_object(::Unicorn::HttpServer).to_a end diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb index 381f1dd4e55..5663b9f20cf 100644 --- a/lib/gitlab/highlight.rb +++ b/lib/gitlab/highlight.rb @@ -68,7 +68,7 @@ module Gitlab end def timeout_time - Sidekiq.server? ? TIMEOUT_BACKGROUND : TIMEOUT_FOREGROUND + Gitlab::Runtime.sidekiq? ? TIMEOUT_BACKGROUND : TIMEOUT_FOREGROUND end def link_dependencies(text, highlighted_text) diff --git a/lib/gitlab/insecure_key_fingerprint.rb b/lib/gitlab/insecure_key_fingerprint.rb index e4f0e9d2c73..7b1cf5e7931 100644 --- a/lib/gitlab/insecure_key_fingerprint.rb +++ b/lib/gitlab/insecure_key_fingerprint.rb @@ -10,6 +10,7 @@ module Gitlab # class InsecureKeyFingerprint attr_accessor :key + alias_attribute :fingerprint_md5, :fingerprint # # Gets the base64 encoded string representing a rsa or dsa key @@ -21,5 +22,9 @@ module Gitlab def fingerprint OpenSSL::Digest::MD5.hexdigest(Base64.decode64(@key)).scan(/../).join(':') end + + def fingerprint_sha256 + Digest::SHA256.base64digest(Base64.decode64(@key)).scan(/../).join('').delete("=") + end end end diff --git a/lib/gitlab/metrics/influx_db.rb b/lib/gitlab/metrics/influx_db.rb index 269d90fa971..1f252572461 100644 --- a/lib/gitlab/metrics/influx_db.rb +++ b/lib/gitlab/metrics/influx_db.rb @@ -150,7 +150,7 @@ module Gitlab # Returns the prefix to use for the name of a series. def series_prefix - @series_prefix ||= Sidekiq.server? ? 'sidekiq_' : 'rails_' + @series_prefix ||= Gitlab::Runtime.sidekiq? ? 'sidekiq_' : 'rails_' end # Allow access from other metrics related middlewares diff --git a/lib/gitlab/metrics/samplers/influx_sampler.rb b/lib/gitlab/metrics/samplers/influx_sampler.rb index 1eae0a7bf45..4e16e335bee 100644 --- a/lib/gitlab/metrics/samplers/influx_sampler.rb +++ b/lib/gitlab/metrics/samplers/influx_sampler.rb @@ -39,14 +39,10 @@ module Gitlab end def add_metric(series, values, tags = {}) - prefix = sidekiq? ? 'sidekiq_' : 'rails_' + prefix = Gitlab::Runtime.sidekiq? ? 'sidekiq_' : 'rails_' @metrics << Metric.new("#{prefix}#{series}", values, tags) end - - def sidekiq? - Sidekiq.server? - end end end end diff --git a/lib/gitlab/metrics/samplers/unicorn_sampler.rb b/lib/gitlab/metrics/samplers/unicorn_sampler.rb index 355f938704e..8c4d150adad 100644 --- a/lib/gitlab/metrics/samplers/unicorn_sampler.rb +++ b/lib/gitlab/metrics/samplers/unicorn_sampler.rb @@ -61,7 +61,7 @@ module Gitlab # it takes around 80ms. The instances of HttpServers are not a subject # to change so we can cache the list of servers. def http_servers - return [] unless defined?(::Unicorn::HttpServer) + return [] unless Gitlab::Runtime.unicorn? @http_servers ||= ObjectSpace.each_object(::Unicorn::HttpServer).to_a end diff --git a/lib/gitlab/redis/wrapper.rb b/lib/gitlab/redis/wrapper.rb index 412d00c6939..beceed3fa75 100644 --- a/lib/gitlab/redis/wrapper.rb +++ b/lib/gitlab/redis/wrapper.rb @@ -22,10 +22,10 @@ module Gitlab def pool_size # heuristic constant 5 should be a config setting somewhere -- related to CPU count? size = 5 - if Sidekiq.server? + if Gitlab::Runtime.sidekiq? # the pool will be used in a multi-threaded context size += Sidekiq.options[:concurrency] - elsif defined?(::Puma) + elsif Gitlab::Runtime.puma? size += Puma.cli_config.options[:max_threads] end diff --git a/lib/gitlab/runtime.rb b/lib/gitlab/runtime.rb new file mode 100644 index 00000000000..07a3afb8834 --- /dev/null +++ b/lib/gitlab/runtime.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module Gitlab + # Provides routines to identify the current runtime as which the application + # executes, such as whether it is an application server and which one. + module Runtime + class << self + def name + matches = [] + matches << :puma if puma? + matches << :unicorn if unicorn? + matches << :console if console? + matches << :sidekiq if sidekiq? + + raise "Ambiguous process match: #{matches}" if matches.size > 1 + + matches.first || :unknown + end + + def puma? + !!(defined?(::Puma) && bin == 'puma') + end + + # For unicorn, we need to check for actual server instances to avoid false positives. + def unicorn? + !!(defined?(::Unicorn) && defined?(::Unicorn::HttpServer)) + end + + def sidekiq? + !!(defined?(::Sidekiq) && Sidekiq.server? && bin == 'sidekiq') + end + + def console? + !!defined?(::Rails::Console) + end + + def app_server? + puma? || unicorn? + end + + def multi_threaded? + puma? || sidekiq? + end + + private + + # Some example values from my system: + # puma: /data/cache/bundle-2.5/bin/puma + # unicorn: unicorn_rails master -E development -c /tmp/unicorn.rb -l 0.0.0.0:8080 + # sidekiq: /data/cache/bundle-2.5/bin/sidekiq + # thin: bin/rails + # console: bin/rails + def script_name + $0 + end + + def bin + File.basename(script_name) + end + end + end +end diff --git a/lib/prometheus/pid_provider.rb b/lib/prometheus/pid_provider.rb index 228639357ac..32beeb0d31e 100644 --- a/lib/prometheus/pid_provider.rb +++ b/lib/prometheus/pid_provider.rb @@ -5,11 +5,11 @@ module Prometheus extend self def worker_id - if Sidekiq.server? + if Gitlab::Runtime.sidekiq? sidekiq_worker_id - elsif defined?(Unicorn::Worker) + elsif Gitlab::Runtime.unicorn? unicorn_worker_id - elsif defined?(::Puma) + elsif Gitlab::Runtime.puma? puma_worker_id else unknown_process_id diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 5c417aa1f29..e5ca8f39991 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -7703,9 +7703,6 @@ msgstr "" msgid "Fingerprint" msgstr "" -msgid "Fingerprint:" -msgstr "" - msgid "Fingerprints" msgstr "" diff --git a/spec/finders/keys_finder_spec.rb b/spec/finders/keys_finder_spec.rb new file mode 100644 index 00000000000..147e6ee3d84 --- /dev/null +++ b/spec/finders/keys_finder_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe KeysFinder do + subject(:keys_finder) { described_class.new(user, params) } + + let(:user) { create(:user) } + let(:fingerprint_type) { 'md5' } + let(:fingerprint) { 'ba:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d1' } + + let(:params) do + { + type: fingerprint_type, + fingerprint: fingerprint + } + end + + let!(:key) do + create(:key, user: user, + key: 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt1016k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=', + fingerprint: 'ba:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d1', + fingerprint_sha256: 'nUhzNyftwADy8AH3wFY31tAKs7HufskYTte2aXo/lCg' + ) + end + + context 'with a regular user' do + it 'raises GitLabAccessDeniedError' do + expect do + keys_finder.execute + end.to raise_error(KeysFinder::GitLabAccessDeniedError) + end + end + + context 'with an admin user' do + let(:user) {create(:admin)} + + context 'with invalid MD5 fingerprint' do + let(:fingerprint) { '11:11:11:11' } + + it 'raises InvalidFingerprint' do + expect { keys_finder.execute } + .to raise_error(KeysFinder::InvalidFingerprint) + end + end + + context 'with invalid SHA fingerprint' do + let(:fingerprint_type) { 'sha256' } + let(:fingerprint) { 'nUhzNyftwAAKs7HufskYTte2g' } + + it 'raises InvalidFingerprint' do + expect { keys_finder.execute } + .to raise_error(KeysFinder::InvalidFingerprint) + end + end + + context 'with valid MD5 params' do + it 'returns key if the fingerprint is found' do + result = keys_finder.execute + + expect(result).to eq(key) + expect(key.user).to eq(user) + end + end + + context 'with valid SHA256 params' do + let(:fingerprint) { 'ba:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d1' } + + it 'returns key if the fingerprint is found' do + result = keys_finder.execute + + expect(result).to eq(key) + expect(key.user).to eq(user) + end + end + end +end diff --git a/spec/initializers/database_config_spec.rb b/spec/initializers/database_config_spec.rb index a5a074f5884..9200a625b38 100644 --- a/spec/initializers/database_config_spec.rb +++ b/spec/initializers/database_config_spec.rb @@ -16,6 +16,7 @@ describe 'Database config initializer' do let(:puma_options) { { max_threads: 8 } } before do + allow(Gitlab::Runtime).to receive(:puma?).and_return(true) stub_const("Puma", puma) allow(puma).to receive_message_chain(:cli_config, :options).and_return(puma_options) end diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb index 9ebd34140c1..80c1493d01b 100644 --- a/spec/lib/gitlab/gitaly_client_spec.rb +++ b/spec/lib/gitlab/gitaly_client_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::GitalyClient do context 'running in Unicorn' do before do - stub_const('Unicorn', 1) + allow(Gitlab::Runtime).to receive(:unicorn?).and_return(true) end it { expect(subject.long_timeout).to eq(55) } @@ -34,7 +34,7 @@ describe Gitlab::GitalyClient do context 'running in Puma' do before do - stub_const('Puma', 1) + allow(Gitlab::Runtime).to receive(:puma?).and_return(true) end it { expect(subject.long_timeout).to eq(55) } diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb index cd593390821..5d43023502c 100644 --- a/spec/lib/gitlab/gpg_spec.rb +++ b/spec/lib/gitlab/gpg_spec.rb @@ -236,7 +236,7 @@ describe Gitlab::Gpg do context 'when running in Sidekiq' do before do - allow(Sidekiq).to receive(:server?).and_return(true) + allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true) end it_behaves_like 'multiple deletion attempts of the tmp-dir', described_class::BG_CLEANUP_RUNTIME_S diff --git a/spec/lib/gitlab/health_checks/puma_check_spec.rb b/spec/lib/gitlab/health_checks/puma_check_spec.rb index dd052a4dd2c..93ef81978a8 100644 --- a/spec/lib/gitlab/health_checks/puma_check_spec.rb +++ b/spec/lib/gitlab/health_checks/puma_check_spec.rb @@ -22,6 +22,7 @@ describe Gitlab::HealthChecks::PumaCheck do context 'when Puma is not loaded' do before do + allow(Gitlab::Runtime).to receive(:puma?).and_return(false) hide_const('Puma') end @@ -33,6 +34,7 @@ describe Gitlab::HealthChecks::PumaCheck do context 'when Puma is loaded' do before do + allow(Gitlab::Runtime).to receive(:puma?).and_return(true) stub_const('Puma', Module.new) end diff --git a/spec/lib/gitlab/health_checks/unicorn_check_spec.rb b/spec/lib/gitlab/health_checks/unicorn_check_spec.rb index 931b61cb168..7c57b6f1ca5 100644 --- a/spec/lib/gitlab/health_checks/unicorn_check_spec.rb +++ b/spec/lib/gitlab/health_checks/unicorn_check_spec.rb @@ -26,6 +26,7 @@ describe Gitlab::HealthChecks::UnicornCheck do context 'when Unicorn is not loaded' do before do + allow(Gitlab::Runtime).to receive(:unicorn?).and_return(false) hide_const('Unicorn') end @@ -39,6 +40,7 @@ describe Gitlab::HealthChecks::UnicornCheck do let(:http_server_class) { Struct.new(:worker_processes) } before do + allow(Gitlab::Runtime).to receive(:unicorn?).and_return(true) stub_const('Unicorn::HttpServer', http_server_class) end diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb index 5a45d724b83..2140cbae488 100644 --- a/spec/lib/gitlab/highlight_spec.rb +++ b/spec/lib/gitlab/highlight_spec.rb @@ -111,7 +111,7 @@ describe Gitlab::Highlight do end it 'utilizes longer timeout for sidekiq' do - allow(Sidekiq).to receive(:server?).and_return(true) + allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true) expect(Timeout).to receive(:timeout).with(described_class::TIMEOUT_BACKGROUND).and_call_original subject.highlight("Content") diff --git a/spec/lib/gitlab/insecure_key_fingerprint_spec.rb b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb index 7f20ae98b06..8d0422bae9f 100644 --- a/spec/lib/gitlab/insecure_key_fingerprint_spec.rb +++ b/spec/lib/gitlab/insecure_key_fingerprint_spec.rb @@ -11,10 +11,17 @@ describe Gitlab::InsecureKeyFingerprint do end let(:fingerprint) { "3f:a2:ee:de:b5:de:53:c3:aa:2f:9c:45:24:4c:47:7b" } + let(:fingerprint_sha256) { "MQHWhS9nhzUezUdD42ytxubZoBKrZLbyBZzxCkmnxXc" } describe "#fingerprint" do it "generates the key's fingerprint" do - expect(described_class.new(key.split[1]).fingerprint).to eq(fingerprint) + expect(described_class.new(key.split[1]).fingerprint_md5).to eq(fingerprint) + end + end + + describe "#fingerprint" do + it "generates the key's fingerprint" do + expect(described_class.new(key.split[1]).fingerprint_sha256).to eq(fingerprint_sha256) end end end diff --git a/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb index 2d4b27a6ac1..939c057c342 100644 --- a/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb @@ -63,7 +63,7 @@ describe Gitlab::Metrics::Samplers::InfluxSampler do describe '#add_metric' do it 'prefixes the series name for a Rails process' do - expect(sampler).to receive(:sidekiq?).and_return(false) + expect(Gitlab::Runtime).to receive(:sidekiq?).and_return(false) expect(Gitlab::Metrics::Metric).to receive(:new) .with('rails_cats', { value: 10 }, {}) @@ -73,7 +73,7 @@ describe Gitlab::Metrics::Samplers::InfluxSampler do end it 'prefixes the series name for a Sidekiq process' do - expect(sampler).to receive(:sidekiq?).and_return(true) + expect(Gitlab::Runtime).to receive(:sidekiq?).and_return(true) expect(Gitlab::Metrics::Metric).to receive(:new) .with('sidekiq_cats', { value: 10 }, {}) diff --git a/spec/lib/gitlab/runtime_spec.rb b/spec/lib/gitlab/runtime_spec.rb new file mode 100644 index 00000000000..914c0fe2be7 --- /dev/null +++ b/spec/lib/gitlab/runtime_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Runtime do + REAL_PATH = $0 + + after(:all) do + $0 = REAL_PATH + end + + context "when unknown" do + it "identifies as :unknown" do + expect(subject.name).to eq(:unknown) + end + end + + context "on multiple matches" do + before do + $0 = '/data/cache/bundle-2.5/bin/puma' + stub_const('::Puma', double) + stub_const('::Rails::Console', double) + end + + it "raises an exception when trying to identify" do + expect { subject.name }.to raise_error(RuntimeError, "Ambiguous process match: [:puma, :console]") + end + end + + context "puma" do + let(:puma_type) { double('::Puma') } + + before do + $0 = '/data/cache/bundle-2.5/bin/puma' + stub_const('::Puma', puma_type) + end + + it "identifies itself" do + expect(subject.name).to eq(:puma) + expect(subject.puma?).to be(true) + end + + it "does not identify as others" do + expect(subject.unicorn?).to be(false) + expect(subject.sidekiq?).to be(false) + expect(subject.console?).to be(false) + end + end + + context "unicorn" do + let(:unicorn_type) { Module.new } + let(:unicorn_server_type) { Class.new } + + before do + $0 = 'unicorn_rails master -E development -c /tmp/unicorn.rb -l 0.0.0.0:8080' + stub_const('::Unicorn', unicorn_type) + stub_const('::Unicorn::HttpServer', unicorn_server_type) + end + + it "identifies itself" do + expect(subject.name).to eq(:unicorn) + expect(subject.unicorn?).to be(true) + end + + it "does not identify as others" do + expect(subject.puma?).to be(false) + expect(subject.sidekiq?).to be(false) + expect(subject.console?).to be(false) + end + end + + context "sidekiq" do + let(:sidekiq_type) { double('::Sidekiq') } + + before do + $0 = '/data/cache/bundle-2.5/bin/sidekiq' + stub_const('::Sidekiq', sidekiq_type) + allow(sidekiq_type).to receive(:server?).and_return(true) + end + + it "identifies itself" do + expect(subject.name).to eq(:sidekiq) + expect(subject.sidekiq?).to be(true) + end + + it "does not identify as others" do + expect(subject.unicorn?).to be(false) + expect(subject.puma?).to be(false) + expect(subject.console?).to be(false) + end + end + + context "console" do + let(:console_type) { double('::Rails::Console') } + + before do + $0 = 'bin/rails' + stub_const('::Rails::Console', console_type) + end + + it "identifies itself" do + expect(subject.name).to eq(:console) + expect(subject.console?).to be(true) + end + + it "does not identify as others" do + expect(subject.unicorn?).to be(false) + expect(subject.sidekiq?).to be(false) + expect(subject.puma?).to be(false) + end + end +end diff --git a/spec/lib/gitlab/ssh_public_key_spec.rb b/spec/lib/gitlab/ssh_public_key_spec.rb index f8becb0c796..08e008c82d9 100644 --- a/spec/lib/gitlab/ssh_public_key_spec.rb +++ b/spec/lib/gitlab/ssh_public_key_spec.rb @@ -183,6 +183,34 @@ describe Gitlab::SSHPublicKey, lib: true do end end + describe '#fingerprint in SHA256 format' do + subject { public_key.fingerprint("SHA256").gsub("SHA256:", "") if public_key.fingerprint("SHA256") } + + where(:factory, :fingerprint_sha256) do + [ + [:rsa_key_2048, 'GdtgO0eHbwLB+mK47zblkoXujkqKRZjgMQrHH6Kks3E'], + [:rsa_key_4096, 'ByDU7hQ1JB95l6p53rHrffc4eXvEtqGUtQhS+Dhyy7g'], + [:rsa_key_5120, 'PCCupLbFHScm4AbEufbGDvhBU27IM0MVAor715qKQK8'], + [:rsa_key_8192, 'CtHFQAS+9Hb8z4vrv4gVQPsHjNN0WIZhWODaB1mQLs4'], + [:dsa_key_2048, '+a3DQ7cU5GM+gaYOfmc0VWNnykHQSuth3VRcCpWuYNI'], + [:ecdsa_key_256, 'C+I5k3D+IGeM6k5iBR1ZsphqTKV+7uvL/XZ5hcrTr7g'], + [:ed25519_key_256, 'DCKAjzxWrdOTjaGKBBjtCW8qY5++GaiAJflrHPmp6W0'] + ] + end + + with_them do + let(:key) { attributes_for(factory)[:key] } + + it { is_expected.to eq(fingerprint_sha256) } + end + + context 'with an invalid SSH key' do + let(:key) { 'this is not a key' } + + it { is_expected.to be_nil } + end + end + describe '#key_text' do let(:key) { 'this is not a key' } diff --git a/spec/lib/prometheus/pid_provider_spec.rb b/spec/lib/prometheus/pid_provider_spec.rb index 6fdc11b14c4..5a17f25f144 100644 --- a/spec/lib/prometheus/pid_provider_spec.rb +++ b/spec/lib/prometheus/pid_provider_spec.rb @@ -6,16 +6,13 @@ describe Prometheus::PidProvider do describe '.worker_id' do subject { described_class.worker_id } - let(:sidekiq_module) { Module.new } - before do - allow(sidekiq_module).to receive(:server?).and_return(false) - stub_const('Sidekiq', sidekiq_module) + allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(false) end context 'when running in Sidekiq server mode' do before do - expect(Sidekiq).to receive(:server?).and_return(true) + allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true) end context 'in a clustered setup' do @@ -33,8 +30,7 @@ describe Prometheus::PidProvider do context 'when running in Unicorn mode' do before do - stub_const('Unicorn::Worker', Class.new) - hide_const('Puma') + allow(Gitlab::Runtime).to receive(:unicorn?).and_return(true) expect(described_class).to receive(:process_name) .at_least(:once) @@ -94,8 +90,7 @@ describe Prometheus::PidProvider do context 'when running in Puma mode' do before do - stub_const('Puma', Module.new) - hide_const('Unicorn::Worker') + allow(Gitlab::Runtime).to receive(:puma?).and_return(true) expect(described_class).to receive(:process_name) .at_least(:once) @@ -116,11 +111,6 @@ describe Prometheus::PidProvider do end context 'when running in unknown mode' do - before do - hide_const('Puma') - hide_const('Unicorn::Worker') - end - it { is_expected.to eq "process_#{Process.pid}" } end end diff --git a/spec/models/concerns/sha256_attribute_spec.rb b/spec/models/concerns/sha256_attribute_spec.rb new file mode 100644 index 00000000000..213723c2dcb --- /dev/null +++ b/spec/models/concerns/sha256_attribute_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Sha256Attribute do + let(:model) { Class.new { include Sha256Attribute } } + + before do + columns = [ + double(:column, name: 'name', type: :text), + double(:column, name: 'sha256', type: :binary) + ] + + allow(model).to receive(:columns).and_return(columns) + end + + describe '#sha_attribute' do + context 'when in non-production' do + before do + stub_rails_env('development') + end + + context 'when the table exists' do + before do + allow(model).to receive(:table_exists?).and_return(true) + end + + it 'defines a SHA attribute for a binary column' do + expect(model).to receive(:attribute) + .with(:sha256, an_instance_of(Gitlab::Database::Sha256Attribute)) + + model.sha256_attribute(:sha256) + end + + it 'raises ArgumentError when the column type is not :binary' do + expect { model.sha256_attribute(:name) }.to raise_error(ArgumentError) + end + end + + context 'when the table does not exist' do + it 'allows the attribute to be added and issues a warning' do + allow(model).to receive(:table_exists?).and_return(false) + + expect(model).not_to receive(:columns) + expect(model).to receive(:attribute) + expect(model).to receive(:warn) + + model.sha256_attribute(:name) + end + end + + context 'when the column does not exist' do + it 'allows the attribute to be added and issues a warning' do + allow(model).to receive(:table_exists?).and_return(true) + + expect(model).to receive(:columns) + expect(model).to receive(:attribute) + expect(model).to receive(:warn) + + model.sha256_attribute(:no_name) + end + end + + context 'when other execeptions are raised' do + it 'logs and re-rasises the error' do + allow(model).to receive(:table_exists?).and_raise(ActiveRecord::NoDatabaseError.new('does not exist')) + + expect(model).not_to receive(:columns) + expect(model).not_to receive(:attribute) + expect(Gitlab::AppLogger).to receive(:error) + + expect { model.sha256_attribute(:name) }.to raise_error(ActiveRecord::NoDatabaseError) + end + end + end + + context 'when in production' do + before do + stub_rails_env('production') + end + + it 'defines a SHA attribute' do + expect(model).not_to receive(:table_exists?) + expect(model).not_to receive(:columns) + expect(model).to receive(:attribute).with(:sha256, an_instance_of(Gitlab::Database::Sha256Attribute)) + + model.sha256_attribute(:sha256) + end + end + end +end diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index a0b6eff88d5..559dc95768a 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -92,6 +92,7 @@ describe Key, :mailer do with_them do let!(:key) { create(factory) } let!(:original_fingerprint) { key.fingerprint } + let!(:original_fingerprint_sha256) { key.fingerprint_sha256 } it 'accepts a key with blank space characters after stripping them' do modified_key = key.key.insert(100, chars.first).insert(40, chars.last) @@ -104,6 +105,8 @@ describe Key, :mailer do expect(content).not_to match(/\s/) expect(original_fingerprint).to eq(key.fingerprint) + expect(original_fingerprint).to eq(key.fingerprint_md5) + expect(original_fingerprint_sha256).to eq(key.fingerprint_sha256) end end end diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb index 6802a0cfdab..f7da1abcfdf 100644 --- a/spec/requests/api/keys_spec.rb +++ b/spec/requests/api/keys_spec.rb @@ -25,7 +25,6 @@ describe API::Keys do it 'returns single ssh key with user information' do user.keys << key - user.save get api("/keys/#{key.id}", admin) expect(response).to have_gitlab_http_status(200) expect(json_response['title']).to eq(key.title) @@ -40,4 +39,73 @@ describe API::Keys do end end end + + describe 'GET /keys?fingerprint=' do + it 'returns authentication error' do + get api("/keys?fingerprint=#{key.fingerprint}") + + expect(response).to have_gitlab_http_status(401) + end + + it 'returns authentication error when authenticated as user' do + get api("/keys?fingerprint=#{key.fingerprint}", user) + + expect(response).to have_gitlab_http_status(403) + end + + context 'when authenticated as admin' do + it 'returns 404 for non-existing SSH md5 fingerprint' do + get api("/keys?fingerprint=11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11", admin) + + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq('404 Key Not Found') + end + + it 'returns 404 for non-existing SSH sha256 fingerprint' do + get api("/keys?fingerprint=#{URI.encode_www_form_component("SHA256:nUhzNyftwADy8AH3wFY31tAKs7HufskYTte2aXo1lCg")}", admin) + + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq('404 Key Not Found') + end + + it 'returns user if SSH md5 fingerprint found' do + user.keys << key + + get api("/keys?fingerprint=#{key.fingerprint}", admin) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['title']).to eq(key.title) + expect(json_response['user']['id']).to eq(user.id) + expect(json_response['user']['username']).to eq(user.username) + end + + it 'returns user if SSH sha256 fingerprint found' do + user.keys << key + + get api("/keys?fingerprint=#{URI.encode_www_form_component("SHA256:" + key.fingerprint_sha256)}", admin) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['title']).to eq(key.title) + expect(json_response['user']['id']).to eq(user.id) + expect(json_response['user']['username']).to eq(user.username) + end + + it 'returns user if SSH sha256 fingerprint found' do + user.keys << key + + get api("/keys?fingerprint=#{URI.encode_www_form_component("sha256:" + key.fingerprint_sha256)}", admin) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['title']).to eq(key.title) + expect(json_response['user']['id']).to eq(user.id) + expect(json_response['user']['username']).to eq(user.username) + end + + it "does not include the user's `is_admin` flag" do + get api("/keys?fingerprint=#{key.fingerprint}", admin) + + expect(json_response['user']['is_admin']).to be_nil + end + end + end end diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb index 19d7b84a3ce..e7f005cff0b 100644 --- a/spec/services/git/branch_push_service_spec.rb +++ b/spec/services/git/branch_push_service_spec.rb @@ -108,7 +108,7 @@ describe Git::BranchPushService, services: true do end it 'reports an error' do - allow(Sidekiq).to receive(:server?).and_return(true) + allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true) expect(Sidekiq.logger).to receive(:warn) expect { subject }.not_to change { Ci::Pipeline.count } diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/redis/redis_shared_examples.rb index 97a23f02b3e..e079c32d6ae 100644 --- a/spec/support/redis/redis_shared_examples.rb +++ b/spec/support/redis/redis_shared_examples.rb @@ -118,7 +118,7 @@ RSpec.shared_examples "redis_shared_examples" do context 'when running not on sidekiq workers' do before do - allow(Sidekiq).to receive(:server?).and_return(false) + allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(false) end it 'instantiates a connection pool with size 5' do @@ -130,7 +130,7 @@ RSpec.shared_examples "redis_shared_examples" do context 'when running on sidekiq workers' do before do - allow(Sidekiq).to receive(:server?).and_return(true) + allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true) allow(Sidekiq).to receive(:options).and_return({ concurrency: 18 }) end |