Add settings for minimum key strength and allowed key type
This is an amalgamation of: * Cory Hinshaw: Initial implementation !5552 * Rémy Coutable: Updates !9350 * Nick Thomas: Resolve conflicts and add ED25519 support !13712
This commit is contained in:
parent
81f08d30e6
commit
b0f982fbdf
26 changed files with 704 additions and 142 deletions
|
@ -66,6 +66,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
params[:application_setting][:allowed_key_types]&.delete('')
|
||||
|
||||
enabled_oauth_sign_in_sources = params[:application_setting].delete(:enabled_oauth_sign_in_sources)
|
||||
|
||||
params[:application_setting][:disabled_oauth_sign_in_sources] =
|
||||
|
@ -83,6 +85,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
|||
def visible_application_setting_attributes
|
||||
ApplicationSettingsHelper.visible_attributes + [
|
||||
:domain_blacklist_file,
|
||||
allowed_key_types: [],
|
||||
disabled_oauth_sign_in_sources: [],
|
||||
import_sources: [],
|
||||
repository_storages: [],
|
||||
|
|
|
@ -81,6 +81,20 @@ module ApplicationSettingsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def allowed_key_types_checkboxes(help_block_id)
|
||||
Gitlab::SSHPublicKey.technology_names.map do |type|
|
||||
checked = current_application_settings.allowed_key_types.include?(type)
|
||||
checkbox_id = "allowed_key_types-#{type}"
|
||||
|
||||
label_tag(checkbox_id, class: checked ? 'active' : nil) do
|
||||
check_box_tag('application_setting[allowed_key_types][]', type, checked,
|
||||
autocomplete: 'off',
|
||||
'aria-describedby' => help_block_id,
|
||||
id: checkbox_id) + type.upcase
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def repository_storages_options_for_select
|
||||
options = Gitlab.config.repositories.storages.map do |name, storage|
|
||||
["#{name} - #{storage['path']}", name]
|
||||
|
@ -141,6 +155,10 @@ module ApplicationSettingsHelper
|
|||
:metrics_port,
|
||||
:metrics_sample_interval,
|
||||
:metrics_timeout,
|
||||
:minimum_dsa_bits,
|
||||
:minimum_ecdsa_bits,
|
||||
:minimum_ed25519_bits,
|
||||
:minimum_rsa_bits,
|
||||
:password_authentication_enabled,
|
||||
:performance_bar_allowed_group_id,
|
||||
:performance_bar_enabled,
|
||||
|
|
|
@ -20,6 +20,7 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
serialize :domain_blacklist, Array # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :sidekiq_throttling_queues, Array # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :allowed_key_types, Array # rubocop:disable Cop/ActiveRecordSerialize
|
||||
|
||||
cache_markdown_field :sign_in_text
|
||||
cache_markdown_field :help_page_text
|
||||
|
@ -146,6 +147,24 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
presence: true,
|
||||
numericality: { greater_than_or_equal_to: 0 }
|
||||
|
||||
validates :allowed_key_types, presence: true
|
||||
|
||||
validates :minimum_rsa_bits,
|
||||
presence: true,
|
||||
inclusion: { in: Gitlab::SSHPublicKey.allowed_sizes('rsa') }
|
||||
|
||||
validates :minimum_dsa_bits,
|
||||
presence: true,
|
||||
inclusion: { in: Gitlab::SSHPublicKey.allowed_sizes('dsa') }
|
||||
|
||||
validates :minimum_ecdsa_bits,
|
||||
presence: true,
|
||||
inclusion: { in: Gitlab::SSHPublicKey.allowed_sizes('ecdsa') }
|
||||
|
||||
validates :minimum_ed25519_bits,
|
||||
presence: true,
|
||||
inclusion: { in: Gitlab::SSHPublicKey.allowed_sizes('ed25519') }
|
||||
|
||||
validates_each :restricted_visibility_levels do |record, attr, value|
|
||||
value&.each do |level|
|
||||
unless Gitlab::VisibilityLevel.options.value?(level)
|
||||
|
@ -170,7 +189,16 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
validates_each :allowed_key_types do |record, attr, value|
|
||||
value&.each do |type|
|
||||
unless Gitlab::SSHPublicKey.allowed_type?(type)
|
||||
record.errors.add(attr, "'#{type}' is not a valid SSH key type")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
before_validation :ensure_uuid!
|
||||
|
||||
before_save :ensure_runners_registration_token
|
||||
before_save :ensure_health_check_access_token
|
||||
|
||||
|
@ -212,6 +240,7 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
{
|
||||
after_sign_up_text: nil,
|
||||
akismet_enabled: false,
|
||||
allowed_key_types: Gitlab::SSHPublicKey.technology_names,
|
||||
container_registry_token_expire_delay: 5,
|
||||
default_artifacts_expire_in: '30 days',
|
||||
default_branch_protection: Settings.gitlab['default_branch_protection'],
|
||||
|
@ -239,6 +268,10 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
max_attachment_size: Settings.gitlab['max_attachment_size'],
|
||||
password_authentication_enabled: Settings.gitlab['password_authentication_enabled'],
|
||||
performance_bar_allowed_group_id: nil,
|
||||
minimum_rsa_bits: 1024,
|
||||
minimum_dsa_bits: 1024,
|
||||
minimum_ecdsa_bits: 256,
|
||||
minimum_ed25519_bits: 256,
|
||||
plantuml_enabled: false,
|
||||
plantuml_url: nil,
|
||||
project_export_enabled: true,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
require 'digest/md5'
|
||||
|
||||
class Key < ActiveRecord::Base
|
||||
include AfterCommitQueue
|
||||
include Gitlab::CurrentSettings
|
||||
include Sortable
|
||||
|
||||
LAST_USED_AT_REFRESH_TIME = 1.day.to_i
|
||||
|
@ -12,14 +14,18 @@ class Key < ActiveRecord::Base
|
|||
validates :title,
|
||||
presence: true,
|
||||
length: { maximum: 255 }
|
||||
|
||||
validates :key,
|
||||
presence: true,
|
||||
length: { maximum: 5000 },
|
||||
format: { with: /\A(ssh|ecdsa)-.*\Z/ }
|
||||
|
||||
validates :fingerprint,
|
||||
uniqueness: true,
|
||||
presence: { message: 'cannot be generated' }
|
||||
|
||||
validate :key_meets_minimum_bit_length, :key_type_is_allowed
|
||||
|
||||
delegate :name, :email, to: :user, prefix: true
|
||||
|
||||
after_commit :add_to_shell, on: :create
|
||||
|
@ -80,6 +86,10 @@ class Key < ActiveRecord::Base
|
|||
SystemHooksService.new.execute_hooks_for(self, :destroy)
|
||||
end
|
||||
|
||||
def public_key
|
||||
@public_key ||= Gitlab::SSHPublicKey.new(key)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_fingerprint
|
||||
|
@ -87,7 +97,40 @@ class Key < ActiveRecord::Base
|
|||
|
||||
return unless self.key.present?
|
||||
|
||||
self.fingerprint = Gitlab::KeyFingerprint.new(self.key).fingerprint
|
||||
self.fingerprint = public_key.fingerprint
|
||||
end
|
||||
|
||||
def key_meets_minimum_bit_length
|
||||
case public_key.type
|
||||
when :rsa
|
||||
if public_key.bits < current_application_settings.minimum_rsa_bits
|
||||
errors.add(:key, "length must be at least #{current_application_settings.minimum_rsa_bits} bits")
|
||||
end
|
||||
when :dsa
|
||||
if public_key.bits < current_application_settings.minimum_dsa_bits
|
||||
errors.add(:key, "length must be at least #{current_application_settings.minimum_dsa_bits} bits")
|
||||
end
|
||||
when :ecdsa
|
||||
if public_key.bits < current_application_settings.minimum_ecdsa_bits
|
||||
errors.add(:key, "elliptic curve size must be at least #{current_application_settings.minimum_ecdsa_bits} bits")
|
||||
end
|
||||
when :ed25519
|
||||
if public_key.bits < current_application_settings.minimum_ed25519_bits
|
||||
errors.add(:key, "length must be at least #{current_application_settings.minimum_ed25519_bits} bits")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def key_type_is_allowed
|
||||
unless current_application_settings.allowed_key_types.include?(public_key.type.to_s)
|
||||
allowed_types =
|
||||
current_application_settings
|
||||
.allowed_key_types
|
||||
.map(&:upcase)
|
||||
.to_sentence(last_word_connector: ', or ', two_words_connector: ' or ')
|
||||
|
||||
errors.add(:key, "type is not allowed. Must be #{allowed_types}")
|
||||
end
|
||||
end
|
||||
|
||||
def notify_user
|
||||
|
|
|
@ -42,12 +42,7 @@
|
|||
= link_to "(?)", help_page_path("integration/bitbucket")
|
||||
and GitLab.com
|
||||
= link_to "(?)", help_page_path("integration/gitlab")
|
||||
.form-group
|
||||
%label.control-label.col-sm-2 Enabled Git access protocols
|
||||
.col-sm-10
|
||||
= select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control')
|
||||
%span.help-block#clone-protocol-help
|
||||
Allow only the selected protocols to be used for Git access.
|
||||
|
||||
.form-group
|
||||
.col-sm-offset-2.col-sm-10
|
||||
.checkbox
|
||||
|
@ -55,6 +50,50 @@
|
|||
= f.check_box :project_export_enabled
|
||||
Project export enabled
|
||||
|
||||
.form-group
|
||||
%label.control-label.col-sm-2 Enabled Git access protocols
|
||||
.col-sm-10
|
||||
= select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control')
|
||||
%span.help-block#clone-protocol-help
|
||||
Allow only the selected protocols to be used for Git access.
|
||||
|
||||
.form-group
|
||||
= f.label :allowed_key_types, 'Allowed SSH keys', class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= hidden_field_tag 'application_setting[allowed_key_types][]', nil, id: 'allowed_key_types-none'
|
||||
- allowed_key_types_checkboxes('allowed-key-types-help').each do |key_type_checkbox|
|
||||
.checkbox= key_type_checkbox
|
||||
%span.help-block#allowed-key-types-help
|
||||
Only SSH keys with allowed algorithms can be uploaded.
|
||||
|
||||
.form-group
|
||||
= f.label :minimum_rsa_bits, 'Minimum RSA key length', class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.select :minimum_rsa_bits, Gitlab::SSHPublicKey.allowed_sizes('rsa'), {}, class: 'form-control'
|
||||
.help-block
|
||||
The minimum length for user RSA SSH keys (in bits)
|
||||
|
||||
.form-group
|
||||
= f.label :minimum_dsa_bits, 'Minimum DSA key length', class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.select :minimum_dsa_bits, Gitlab::SSHPublicKey.allowed_sizes('dsa'), {}, class: 'form-control'
|
||||
.help-block
|
||||
The minimum length for user DSA SSH keys (in bits)
|
||||
|
||||
.form-group
|
||||
= f.label :minimum_ecdsa_bits, 'Minimum ECDSA key length', class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.select :minimum_ecdsa_bits, Gitlab::SSHPublicKey.allowed_sizes('ecdsa'), {}, class: 'form-control'
|
||||
.help-block
|
||||
The minimum elliptic curve size for user ECDSA SSH keys (in bits)
|
||||
|
||||
.form-group
|
||||
= f.label :minimum_ed25519_bits, 'Minimum ED25519 key length', class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.select :minimum_ed25519_bits, Gitlab::SSHPublicKey.allowed_sizes('ed25519'), {}, class: 'form-control'
|
||||
.help-block
|
||||
The minimum length for user ED25519 SSH keys (in bits)
|
||||
|
||||
%fieldset
|
||||
%legend Account and Limit Settings
|
||||
.form-group
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add settings for minimum key strength and allowed key type
|
||||
merge_request: 13712
|
||||
author: Cory Hinshaw
|
||||
type: added
|
|
@ -0,0 +1,24 @@
|
|||
class AddMinimumKeyLengthToApplicationSettings < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
# Set this constant to true if this migration requires downtime.
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_column_with_default :application_settings, :minimum_rsa_bits, :integer, default: 1024
|
||||
add_column_with_default :application_settings, :minimum_dsa_bits, :integer, default: 1024
|
||||
add_column_with_default :application_settings, :minimum_ecdsa_bits, :integer, default: 256
|
||||
add_column_with_default :application_settings, :minimum_ed25519_bits, :integer, default: 256
|
||||
add_column_with_default :application_settings, :allowed_key_types, :string, default: %w[rsa dsa ecdsa ed25519].to_yaml
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :application_settings, :minimum_rsa_bits
|
||||
remove_column :application_settings, :minimum_dsa_bits
|
||||
remove_column :application_settings, :minimum_ecdsa_bits
|
||||
remove_column :application_settings, :minimum_ed25519_bits
|
||||
remove_column :application_settings, :allowed_key_types
|
||||
end
|
||||
end
|
|
@ -129,6 +129,11 @@ ActiveRecord::Schema.define(version: 20170824162758) do
|
|||
t.boolean "password_authentication_enabled"
|
||||
t.boolean "project_export_enabled", default: true, null: false
|
||||
t.boolean "hashed_storage_enabled", default: false, null: false
|
||||
t.integer "minimum_rsa_bits", default: 1024, null: false
|
||||
t.integer "minimum_dsa_bits", default: 1024, null: false
|
||||
t.integer "minimum_ecdsa_bits", default: 256, null: false
|
||||
t.integer "minimum_ed25519_bits", default: 256, null: false
|
||||
t.string "allowed_key_types", default: "---\n- rsa\n- dsa\n- ecdsa\n- ed25519\n", null: false
|
||||
end
|
||||
|
||||
create_table "audit_events", force: :cascade do |t|
|
||||
|
|
|
@ -48,7 +48,12 @@ Example response:
|
|||
"plantuml_enabled": false,
|
||||
"plantuml_url": null,
|
||||
"terminal_max_session_time": 0,
|
||||
"polling_interval_multiplier": 1.0
|
||||
"polling_interval_multiplier": 1.0,
|
||||
"minimum_rsa_bits": 1024,
|
||||
"minimum_dsa_bits": 1024,
|
||||
"minimum_ecdsa_bits": 256,
|
||||
"minimum_ed25519_bits": 256,
|
||||
"allowed_key_types": ["rsa", "dsa", "ecdsa", "ed25519"]
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -88,6 +93,11 @@ PUT /application/settings
|
|||
| `plantuml_url` | string | yes (if `plantuml_enabled` is `true`) | The PlantUML instance URL for integration. |
|
||||
| `terminal_max_session_time` | integer | no | Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time. |
|
||||
| `polling_interval_multiplier` | decimal | no | Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling. |
|
||||
| `minimum_rsa_bits` | integer | no | The minimum allowed bit length of an uploaded RSA key. Default is `1024`.
|
||||
| `minimum_dsa_bits` | integer | no | The minimum allowed bit length of an uploaded DSA key. Default is `1024`.
|
||||
| `minimum_ecdsa_bits` | integer | no | The minimum allowed curve size (in bits) of an uploaded ECDSA key. Default is `256`.
|
||||
| `minimum_ed25519_bits` | integer | no | The minimum allowed curve size (in bits) of an uploaded ED25519 key. Default is `256`.
|
||||
| `allowed_key_types` | array of strings | no | Array of SSH key types accepted by the application. Allowed values are: `rsa`, `dsa`, `ecdsa`, and `ed25519`. Default is `["rsa", "dsa", "ecdsa", "ed25519"]`.
|
||||
|
||||
```bash
|
||||
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/application/settings?signup_enabled=false&default_project_visibility=internal
|
||||
|
@ -125,6 +135,11 @@ Example response:
|
|||
"plantuml_enabled": false,
|
||||
"plantuml_url": null,
|
||||
"terminal_max_session_time": 0,
|
||||
"polling_interval_multiplier": 1.0
|
||||
"polling_interval_multiplier": 1.0,
|
||||
"minimum_rsa_bits": 1024,
|
||||
"minimum_dsa_bits": 1024,
|
||||
"minimum_ecdsa_bits": 256,
|
||||
"minimum_ed25519_bits": 256,
|
||||
"allowed_key_types": ["rsa", "dsa", "ecdsa", "ed25519"]
|
||||
}
|
||||
```
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Security
|
||||
|
||||
- [Password length limits](password_length_limits.md)
|
||||
- [Restrict allowed SSH key technologies and minimum length](ssh_keys_restrictions.md)
|
||||
- [Rack attack](rack_attack.md)
|
||||
- [Webhooks and insecure internal web services](webhooks.md)
|
||||
- [Information exclusivity](information_exclusivity.md)
|
||||
|
|
BIN
doc/security/img/ssh_keys_restrictions_settings.png
Normal file
BIN
doc/security/img/ssh_keys_restrictions_settings.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
18
doc/security/ssh_keys_restrictions.md
Normal file
18
doc/security/ssh_keys_restrictions.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Restrict allowed SSH key technologies and minimum length
|
||||
|
||||
`ssh-keygen` allows users to create RSA keys with as few as 768 bits, which
|
||||
falls well below recommendations from certain standards groups (such as the US
|
||||
NIST). Some organizations deploying Gitlab will need to enforce minimum key
|
||||
strength, either to satisfy internal security policy or for regulatory
|
||||
compliance.
|
||||
|
||||
Similarly, certain standards groups recommend using RSA or ECDSA over the older
|
||||
DSA and administrators may need to limit the allowed SSH key algorithms.
|
||||
|
||||
GitLab allows you to restrict the allowed SSH key technology as well as specify
|
||||
the minimum key length for each technology.
|
||||
|
||||
In the Admin area under **Settings** (`/admin/application_settings`), look for
|
||||
the "Visibility and Access Controls" area:
|
||||
|
||||
![SSH keys restriction admin settings](img/ssh_keys_restrictions_settings.png)
|
|
@ -744,6 +744,7 @@ module API
|
|||
expose(:default_snippet_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_snippet_visibility) }
|
||||
expose(:default_group_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_group_visibility) }
|
||||
expose :password_authentication_enabled, as: :signin_enabled
|
||||
expose :allowed_key_types
|
||||
end
|
||||
|
||||
class Release < Grape::Entity
|
||||
|
|
|
@ -122,6 +122,12 @@ module API
|
|||
optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'
|
||||
optional :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.'
|
||||
|
||||
optional :minimum_rsa_bits, type: Integer, values: Gitlab::SSHPublicKey.allowed_sizes('rsa'), desc: 'The minimum allowed bit length of an uploaded RSA key.'
|
||||
optional :minimum_dsa_bits, type: Integer, values: Gitlab::SSHPublicKey.allowed_sizes('dsa'), desc: 'The minimum allowed bit length of an uploaded DSA key.'
|
||||
optional :minimum_ecdsa_bits, type: Integer, values: Gitlab::SSHPublicKey.allowed_sizes('ecdsa'), desc: 'The minimum allowed curve size (in bits) of an uploaded ECDSA key.'
|
||||
optional :minimum_ed25519_bits, type: Integer, values: Gitlab::SSHPublicKey.allowed_sizes('ed25519'), desc: 'The minimum allowed curve size (in bits) of an uploaded ED25519 key.'
|
||||
optional :allowed_key_types, type: Array[String], values: Gitlab::SSHPublicKey.technology_names, desc: 'The SSH key types accepted by the application (`rsa`, `dsa`, `ecdsa` or `ed25519`).'
|
||||
|
||||
optional(*::ApplicationSettingsHelper.visible_attributes)
|
||||
at_least_one_of(*::ApplicationSettingsHelper.visible_attributes)
|
||||
end
|
||||
|
|
|
@ -34,6 +34,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def check(cmd, changes)
|
||||
check_valid_actor!
|
||||
check_protocol!
|
||||
check_active_user!
|
||||
check_project_accessibility!
|
||||
|
@ -70,6 +71,14 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
def check_valid_actor!
|
||||
return unless actor.is_a?(Key)
|
||||
|
||||
unless actor.valid?
|
||||
raise UnauthorizedError, "Your SSH key #{actor.errors[:key].first}."
|
||||
end
|
||||
end
|
||||
|
||||
def check_protocol!
|
||||
unless protocol_allowed?
|
||||
raise UnauthorizedError, "Git access over #{protocol.upcase} is not allowed"
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
module Gitlab
|
||||
class KeyFingerprint
|
||||
attr_reader :key, :ssh_key
|
||||
|
||||
# Unqualified MD5 fingerprint for compatibility
|
||||
delegate :fingerprint, to: :ssh_key, allow_nil: true
|
||||
|
||||
def initialize(key)
|
||||
@key = key
|
||||
|
||||
@ssh_key =
|
||||
begin
|
||||
Net::SSH::KeyFactory.load_data_public_key(key)
|
||||
rescue Net::SSH::Exception, NotImplementedError
|
||||
end
|
||||
end
|
||||
|
||||
def valid?
|
||||
ssh_key.present?
|
||||
end
|
||||
|
||||
def type
|
||||
return unless valid?
|
||||
|
||||
parts = ssh_key.ssh_type.split('-')
|
||||
parts.shift if parts[0] == 'ssh'
|
||||
|
||||
parts[0].upcase
|
||||
end
|
||||
|
||||
def bits
|
||||
return unless valid?
|
||||
|
||||
case type
|
||||
when 'RSA'
|
||||
ssh_key.n.num_bits
|
||||
when 'DSS', 'DSA'
|
||||
ssh_key.p.num_bits
|
||||
when 'ECDSA'
|
||||
ssh_key.group.order.num_bits
|
||||
when 'ED25519'
|
||||
256
|
||||
else
|
||||
raise "Unsupported key type: #{type}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
84
lib/gitlab/ssh_public_key.rb
Normal file
84
lib/gitlab/ssh_public_key.rb
Normal file
|
@ -0,0 +1,84 @@
|
|||
module Gitlab
|
||||
class SSHPublicKey
|
||||
TYPES = %w[rsa dsa ecdsa ed25519].freeze
|
||||
|
||||
Technology = Struct.new(:name, :allowed_sizes)
|
||||
|
||||
Technologies = [
|
||||
Technology.new('rsa', [1024, 2048, 3072, 4096]),
|
||||
Technology.new('dsa', [1024, 2048, 3072]),
|
||||
Technology.new('ecdsa', [256, 384, 521]),
|
||||
Technology.new('ed25519', [256])
|
||||
].freeze
|
||||
|
||||
def self.technology_names
|
||||
Technologies.map(&:name)
|
||||
end
|
||||
|
||||
def self.technology(name)
|
||||
Technologies.find { |ssh_key_technology| ssh_key_technology.name == name }
|
||||
end
|
||||
private_class_method :technology
|
||||
|
||||
def self.allowed_sizes(name)
|
||||
technology(name).allowed_sizes
|
||||
end
|
||||
|
||||
def self.allowed_type?(type)
|
||||
technology_names.include?(type.to_s)
|
||||
end
|
||||
|
||||
attr_reader :key_text, :key
|
||||
|
||||
# Unqualified MD5 fingerprint for compatibility
|
||||
delegate :fingerprint, to: :key, allow_nil: true
|
||||
|
||||
def initialize(key_text)
|
||||
@key_text = key_text
|
||||
|
||||
@key =
|
||||
begin
|
||||
Net::SSH::KeyFactory.load_data_public_key(key_text)
|
||||
rescue StandardError, NotImplementedError
|
||||
end
|
||||
end
|
||||
|
||||
def valid?
|
||||
key.present?
|
||||
end
|
||||
|
||||
def type
|
||||
return unless valid?
|
||||
|
||||
case key
|
||||
when OpenSSL::PKey::EC
|
||||
:ecdsa
|
||||
when OpenSSL::PKey::RSA
|
||||
:rsa
|
||||
when OpenSSL::PKey::DSA
|
||||
:dsa
|
||||
when Net::SSH::Authentication::ED25519::PubKey
|
||||
:ed25519
|
||||
else
|
||||
raise "Unsupported key type: #{key.class}"
|
||||
end
|
||||
end
|
||||
|
||||
def bits
|
||||
return unless valid?
|
||||
|
||||
case type
|
||||
when :rsa
|
||||
key.n.num_bits
|
||||
when :dsa
|
||||
key.p.num_bits
|
||||
when :ecdsa
|
||||
key.group.order.num_bits
|
||||
when :ed25519
|
||||
256
|
||||
else
|
||||
raise "Unsupported key type: #{type}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,5 +18,54 @@ FactoryGirl.define do
|
|||
factory :write_access_key, class: 'DeployKey' do
|
||||
can_push true
|
||||
end
|
||||
|
||||
factory :rsa_key_2048 do
|
||||
key do
|
||||
'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFf6RYK3qu/RKF/3ndJmL5xgMLp3O9' \
|
||||
'6x8lTay+QGZ0+9FnnAXMdUqBq/ZU6d/gyMB4IaW3nHzM1w049++yAB6UPCzMB8Uo27K5' \
|
||||
'/jyZCtj7Vm9PFNjF/8am1kp46c/SeYicQgQaSBdzIW3UDEa1Ef68qroOlvpi9PYZ/tA7' \
|
||||
'M0YP0K5PXX+E36zaIRnJVMPT3f2k+GnrxtjafZrwFdpOP/Fol5BQLBgcsyiU+LM1SuaC' \
|
||||
'rzd8c9vyaTA1CxrkxaZh+buAi0PmdDtaDrHd42gqZkXCKavyvgM5o2CkQ5LJHCgzpXy0' \
|
||||
'5qNFzmThBSkb+XtoxbyagBiGbVZtSVow6Xa7qewz= dummy@gitlab.com'
|
||||
end
|
||||
|
||||
factory :rsa_deploy_key_2048, class: 'DeployKey'
|
||||
end
|
||||
|
||||
factory :dsa_key_2048 do
|
||||
key do
|
||||
'ssh-dss AAAAB3NzaC1kc3MAAAEBAO/3/NPLA/zSFkMOCaTtGo+uos1flfQ5f038Uk+G' \
|
||||
'Y9AeLGzX+Srhw59GdVXmOQLYBrOt5HdGwqYcmLnE2VurUGmhtfeO5H+3p5pGJbkS0Gxp' \
|
||||
'YH1HRO9lWsncF3Hh1w4lYsDjkclDiSTdfTuN8F4Kb3DXNnVSCieeonp+B25F/CXagyTQ' \
|
||||
'/pvNmHFeYgGCVdnBtFdi+xfxaZ8NKdPrGggzokbKHElDZQ4Xo5EpdcyLajgM7nB2r2Rz' \
|
||||
'OrmeaevKi5lV68ehRa9Yyrb7vxvwiwBwOgqR/mnN7Gnaq1jUdmJY+ct04Qwx37f5jvhv' \
|
||||
'5gA4U40SGMoiHM8RFIN7Ksz0jsyX73MAAAAVALRWOfjfzHpK7KLz4iqDvvTUAevJAAAB' \
|
||||
'AEa9NZ+6y9iQ5erGsdfLTXFrhSefTG0NhghoO/5IFkSGfd8V7kzTvCHaFrcfpEA5kP8t' \
|
||||
'poeOG0TASB6tgGOxm1Bq4Wncry5RORBPJlAVpDGRcvZ931ddH7IgltEInS6za2uH6F/1' \
|
||||
'M1QfKePSLr6xJ1ZLYfP0Og5KTp1x6yMQvfwV0a+XdA+EPgaJWLWp/pWwKWa0oLUgjsIH' \
|
||||
'MYzuOGh5c708uZrmkzqvgtW2NgXhcIroRgynT3IfI2lP2rqqb3uuuE/qH5UCUFO+Dc3H' \
|
||||
'nAFNeQDT/M25AERdPYBAY5a+iPjIgO+jT7BfmfByT+AZTqZySrCyc7nNZL3YgGLK0l6A' \
|
||||
'1GgAAAEBAN9FpFOdIXE+YEZhKl1vPmbcn+b1y5zOl6N4x1B7Q8pD/pLMziWROIS8uLzb' \
|
||||
'aZ0sMIWezHIkxuo1iROMeT+jtCubn7ragaN6AX7nMpxYUH9+mYZZs/fyElt6wCviVhTI' \
|
||||
'zM+u7VdQsnZttOOlQfogHdL+SpeAft0DsfJjlcgQnsLlHQKv6aPqCPYUST2nE7RyW/Ex' \
|
||||
'PrMxLtOWt0/j8RYHbwwqvyeZqBz3ESBgrS9c5tBdBfauwYUV/E7gPLOU3OZFw9ue7o+z' \
|
||||
'wzoTZqW6Xouy5wtWvSLQSLT5XwOslmQz8QMBxD0AQyDfEFGsBCWzmbTgKv9uqrBjubsS' \
|
||||
'Taja+Cf9kMo== dummy@gitlab.com'
|
||||
end
|
||||
end
|
||||
|
||||
factory :ecdsa_key_256 do
|
||||
key do
|
||||
'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYA' \
|
||||
'AABBBJZmkzTgY0fiCQ+DVReyH/fFwTFz0XoR3RUO0u+199H19KFw7mNPxRSMOVS7tEtO' \
|
||||
'Nj3Q7FcZXfqthHvgAzDiHsc= dummy@gitlab.com'
|
||||
end
|
||||
end
|
||||
|
||||
factory :ed25519_key_256 do
|
||||
key do
|
||||
'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETnVTgzqC1gatgSlC4zH6aYt2CAQzgJOhDRvf59ohL6 dummy@gitlab.com'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -79,6 +79,26 @@ feature 'Admin updates settings' do
|
|||
end
|
||||
end
|
||||
|
||||
scenario 'Change Keys settings' do
|
||||
uncheck 'RSA'
|
||||
uncheck 'DSA'
|
||||
uncheck 'ED25519'
|
||||
select '384', from: 'Minimum ECDSA key length'
|
||||
click_on 'Save'
|
||||
|
||||
expect(page).to have_content 'Application settings saved successfully'
|
||||
|
||||
expect(find_field('RSA', checked: false)).not_to be_checked
|
||||
expect(find_field('DSA', checked: false)).not_to be_checked
|
||||
expect(find_field('ED25519', checked: false)).not_to be_checked
|
||||
expect(find_field('Minimum ECDSA key length').value).to eq '384'
|
||||
|
||||
uncheck 'ECDSA'
|
||||
click_on 'Save'
|
||||
|
||||
expect(page).to have_content "Allowed key types can't be blank"
|
||||
end
|
||||
|
||||
def check_all_events
|
||||
page.check('Active')
|
||||
page.check('Push')
|
||||
|
|
|
@ -28,6 +28,22 @@ feature 'Profile > SSH Keys' do
|
|||
expect(page).to have_content("Title: #{attrs[:title]}")
|
||||
expect(page).to have_content(attrs[:key])
|
||||
end
|
||||
|
||||
context 'when only DSA and ECDSA keys are allowed' do
|
||||
before do
|
||||
stub_application_setting(allowed_key_types: %w[dsa ecdsa])
|
||||
end
|
||||
|
||||
scenario 'shows a validation error' do
|
||||
attrs = attributes_for(:key)
|
||||
|
||||
fill_in('Key', with: attrs[:key])
|
||||
fill_in('Title', with: attrs[:title])
|
||||
click_button('Add key')
|
||||
|
||||
expect(page).to have_content('Key type is not allowed. Must be DSA or ECDSA')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scenario 'User sees their keys' do
|
||||
|
|
|
@ -155,6 +155,48 @@ describe Gitlab::GitAccess do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples '#check with a key that is not valid' do
|
||||
before do
|
||||
project.add_master(user)
|
||||
end
|
||||
|
||||
context 'key is too small' do
|
||||
before do
|
||||
stub_application_setting(minimum_rsa_bits: 4096)
|
||||
end
|
||||
|
||||
it 'does not allow keys which are too small' do
|
||||
aggregate_failures do
|
||||
expect(actor).not_to be_valid
|
||||
expect { pull_access_check }.to raise_unauthorized('Your SSH key length must be at least 4096 bits.')
|
||||
expect { push_access_check }.to raise_unauthorized('Your SSH key length must be at least 4096 bits.')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'key type is not allowed' do
|
||||
before do
|
||||
stub_application_setting(allowed_key_types: ['ecdsa'])
|
||||
end
|
||||
|
||||
it 'does not allow keys which are too small' do
|
||||
aggregate_failures do
|
||||
expect(actor).not_to be_valid
|
||||
expect { pull_access_check }.to raise_unauthorized('Your SSH key type is not allowed. Must be ECDSA.')
|
||||
expect { push_access_check }.to raise_unauthorized('Your SSH key type is not allowed. Must be ECDSA.')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like '#check with a key that is not valid' do
|
||||
let(:actor) { build(:rsa_key_2048, user: user) }
|
||||
end
|
||||
|
||||
it_behaves_like '#check with a key that is not valid' do
|
||||
let(:actor) { build(:rsa_deploy_key_2048, user: user) }
|
||||
end
|
||||
|
||||
describe '#check_project_moved!' do
|
||||
before do
|
||||
project.add_master(user)
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::KeyFingerprint, lib: true do
|
||||
KEYS = {
|
||||
rsa:
|
||||
'example.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC5z65PwQ1GE6foJgwk' \
|
||||
'9rmQi/glaXbUeVa5uvQpnZ3Z5+forcI7aTngh3aZ/H2UDP2L70TGy7kKNyp0J3a8/OdG' \
|
||||
'Z08y5yi3JlbjFARO1NyoFEjw2H1SJxeJ43L6zmvTlu+hlK1jSAlidl7enS0ufTlzEEj4' \
|
||||
'iJcuTPKdVzKRgZuTRVm9woWNVKqIrdRC0rJiTinERnfSAp/vNYERMuaoN4oJt8p/NEek' \
|
||||
'rmFoDsQOsyDW5RAnCnjWUU+jFBKDpfkJQ1U2n6BjJewC9dl6ODK639l3yN4WOLZEk4tN' \
|
||||
'UysfbGeF3rmMeflaD6O1Jplpv3YhwVGFNKa7fMq6k3Z0tszTJPYh',
|
||||
ecdsa:
|
||||
'example.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAI' \
|
||||
'bmlzdHAyNTYAAABBBKTJy43NZzJSfNxpv/e2E6Zy3qoHoTQbmOsU5FEfpWfWa1MdTeXQ' \
|
||||
'YvKOi+qz/1AaNx6BK421jGu74JCDJtiZWT8=',
|
||||
ed25519:
|
||||
'@revoked example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjq' \
|
||||
'uxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf',
|
||||
dss:
|
||||
'example.com ssh-dss AAAAB3NzaC1kc3MAAACBAP1/U4EddRIpUt9KnC7s5Of2EbdS' \
|
||||
'PO9EAMMeP4C2USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVClpJ+f' \
|
||||
'6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith1yrv8iID' \
|
||||
'GZ3RSAHHAAAAFQCXYFCPFSMLzLKSuYKi64QL8Fgc9QAAAIEA9+GghdabPd7LvKtcNrhX' \
|
||||
'uXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3zwkyjMim4TwW' \
|
||||
'eotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/hWuWfBpKLZl6A' \
|
||||
'e1UlZAFMO/7PSSoAAACBAJcQ4JODqhuGbXIEpqxetm7PWbdbCcr3y/GzIZ066pRovpL6' \
|
||||
'qm3qCVIym4cyChxWwb8qlyCIi+YRUUWm1z/wiBYT2Vf3S4FXBnyymCkKEaV/EY7+jd4X' \
|
||||
'1bXI58OD2u+bLCB/sInM4fGB8CZUIWT9nJH0Ve9jJUge2ms348/QOJ1+'
|
||||
}.freeze
|
||||
|
||||
MD5_FINGERPRINTS = {
|
||||
rsa: '06:b2:8a:92:df:0e:11:2c:ca:7b:8f:a4:ba:6e:4b:fd',
|
||||
ecdsa: '45:ff:5b:98:9a:b6:8a:41:13:c1:30:8b:09:5e:7b:4e',
|
||||
ed25519: '2e:65:6a:c8:cf:bf:b2:8b:9a:bd:6d:9f:11:5c:12:16',
|
||||
dss: '57:98:86:02:5f:9c:f4:9b:ad:5a:1e:51:92:0e:fd:2b'
|
||||
}.freeze
|
||||
|
||||
BIT_COUNTS = {
|
||||
rsa: 2048,
|
||||
ecdsa: 256,
|
||||
ed25519: 256,
|
||||
dss: 1024
|
||||
}.freeze
|
||||
|
||||
describe '#type' do
|
||||
KEYS.each do |type, key|
|
||||
it "calculates the type of #{type} keys" do
|
||||
calculated_type = described_class.new(key).type
|
||||
|
||||
expect(calculated_type).to eq(type.to_s.upcase)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#fingerprint' do
|
||||
KEYS.each do |type, key|
|
||||
it "calculates the MD5 fingerprint for #{type} keys" do
|
||||
fp = described_class.new(key).fingerprint
|
||||
|
||||
expect(fp).to eq(MD5_FINGERPRINTS[type])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#bits' do
|
||||
KEYS.each do |type, key|
|
||||
it "calculates the number of bits in #{type} keys" do
|
||||
bits = described_class.new(key).bits
|
||||
|
||||
expect(bits).to eq(BIT_COUNTS[type])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#key' do
|
||||
it 'carries the unmodified key data' do
|
||||
key = described_class.new(KEYS[:rsa]).key
|
||||
|
||||
expect(key).to eq(KEYS[:rsa])
|
||||
end
|
||||
end
|
||||
end
|
132
spec/lib/gitlab/ssh_public_key_spec.rb
Normal file
132
spec/lib/gitlab/ssh_public_key_spec.rb
Normal file
|
@ -0,0 +1,132 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::SSHPublicKey, lib: true do
|
||||
let(:key) { attributes_for(:rsa_key_2048)[:key] }
|
||||
let(:public_key) { described_class.new(key) }
|
||||
|
||||
describe '.technology_names' do
|
||||
it 'returns the available technology names' do
|
||||
expect(described_class.technology_names).to eq(%w[rsa dsa ecdsa ed25519])
|
||||
end
|
||||
end
|
||||
|
||||
describe '.allowed_sizes(name)' do
|
||||
where(:name, :sizes) do
|
||||
[
|
||||
['rsa', [1024, 2048, 3072, 4096]],
|
||||
['dsa', [1024, 2048, 3072]],
|
||||
['ecdsa', [256, 384, 521]],
|
||||
['ed25519', [256]]
|
||||
]
|
||||
end
|
||||
|
||||
subject { described_class.allowed_sizes(name) }
|
||||
|
||||
with_them do
|
||||
it { is_expected.to eq(sizes) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.allowed_type?' do
|
||||
it 'determines the key type' do
|
||||
expect(described_class.allowed_type?('foo')).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#valid?' do
|
||||
subject { public_key }
|
||||
|
||||
context 'with a valid SSH key' do
|
||||
it { is_expected.to be_valid }
|
||||
end
|
||||
|
||||
context 'with an invalid SSH key' do
|
||||
let(:key) { 'this is not a key' }
|
||||
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#type' do
|
||||
subject { public_key.type }
|
||||
|
||||
where(:factory, :type) do
|
||||
[
|
||||
[:rsa_key_2048, :rsa],
|
||||
[:dsa_key_2048, :dsa],
|
||||
[:ecdsa_key_256, :ecdsa],
|
||||
[:ed25519_key_256, :ed25519]
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:key) { attributes_for(factory)[:key] }
|
||||
|
||||
it { is_expected.to eq(type) }
|
||||
end
|
||||
|
||||
context 'with an invalid SSH key' do
|
||||
let(:key) { 'this is not a key' }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#bits' do
|
||||
subject { public_key.bits }
|
||||
|
||||
where(:factory, :bits) do
|
||||
[
|
||||
[:rsa_key_2048, 2048],
|
||||
[:dsa_key_2048, 2048],
|
||||
[:ecdsa_key_256, 256],
|
||||
[:ed25519_key_256, 256]
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:key) { attributes_for(factory)[:key] }
|
||||
|
||||
it { is_expected.to eq(bits) }
|
||||
end
|
||||
|
||||
context 'with an invalid SSH key' do
|
||||
let(:key) { 'this is not a key' }
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#fingerprint' do
|
||||
subject { public_key.fingerprint }
|
||||
|
||||
where(:factory, :fingerprint) do
|
||||
[
|
||||
[:rsa_key_2048, '2e:ca:dc:e0:37:29:ed:fc:f0:1d:bf:66:d4:cd:51:b1'],
|
||||
[:dsa_key_2048, 'bc:c1:a4:be:7e:8c:84:56:b3:58:93:53:c6:80:78:8c'],
|
||||
[:ecdsa_key_256, '67:a3:a9:7d:b8:e1:15:d4:80:40:21:34:bb:ed:97:38'],
|
||||
[:ed25519_key_256, 'e6:eb:45:8a:3c:59:35:5f:e9:5b:80:12:be:7e:22:73']
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:key) { attributes_for(factory)[:key] }
|
||||
|
||||
it { is_expected.to eq(fingerprint) }
|
||||
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' }
|
||||
|
||||
it 'carries the unmodified key data' do
|
||||
expect(public_key.key_text).to eq(key)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -72,6 +72,27 @@ describe ApplicationSetting do
|
|||
.is_greater_than(0)
|
||||
end
|
||||
|
||||
it { is_expected.to validate_presence_of(:minimum_rsa_bits) }
|
||||
it { is_expected.to allow_value(*Gitlab::SSHPublicKey.allowed_sizes('rsa')).for(:minimum_rsa_bits) }
|
||||
it { is_expected.not_to allow_value(128).for(:minimum_rsa_bits) }
|
||||
|
||||
it { is_expected.to validate_presence_of(:minimum_dsa_bits) }
|
||||
it { is_expected.to allow_value(*Gitlab::SSHPublicKey.allowed_sizes('dsa')).for(:minimum_dsa_bits) }
|
||||
it { is_expected.not_to allow_value(128).for(:minimum_dsa_bits) }
|
||||
|
||||
it { is_expected.to validate_presence_of(:minimum_ecdsa_bits) }
|
||||
it { is_expected.to allow_value(*Gitlab::SSHPublicKey.allowed_sizes('ecdsa')).for(:minimum_ecdsa_bits) }
|
||||
it { is_expected.not_to allow_value(128).for(:minimum_ecdsa_bits) }
|
||||
|
||||
it { is_expected.to validate_presence_of(:minimum_ed25519_bits) }
|
||||
it { is_expected.to allow_value(*Gitlab::SSHPublicKey.allowed_sizes('ed25519')).for(:minimum_ed25519_bits) }
|
||||
it { is_expected.not_to allow_value(128).for(:minimum_ed25519_bits) }
|
||||
|
||||
describe 'allowed_key_types validations' do
|
||||
it { is_expected.to allow_value(Gitlab::SSHPublicKey.technology_names).for(:allowed_key_types) }
|
||||
it { is_expected.not_to allow_value(['foo']).for(:allowed_key_types) }
|
||||
end
|
||||
|
||||
it_behaves_like 'an object with email-formated attributes', :admin_notification_email do
|
||||
subject { setting }
|
||||
end
|
||||
|
@ -441,4 +462,16 @@ describe ApplicationSetting do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'allowed key types attribute' do
|
||||
it 'set value with array of symbols' do
|
||||
setting.allowed_key_types = [:rsa]
|
||||
expect(setting.allowed_key_types).to contain_exactly(:rsa)
|
||||
end
|
||||
|
||||
it 'get value as array of symbols' do
|
||||
setting.allowed_key_types = ['rsa']
|
||||
expect(setting.allowed_key_types).to eq(['rsa'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Key, :mailer do
|
||||
include Gitlab::CurrentSettings
|
||||
|
||||
describe 'modules' do
|
||||
subject { described_class }
|
||||
it { is_expected.to include_module(Gitlab::CurrentSettings) }
|
||||
end
|
||||
|
||||
describe "Associations" do
|
||||
it { is_expected.to belong_to(:user) }
|
||||
end
|
||||
|
@ -11,8 +18,10 @@ describe Key, :mailer do
|
|||
|
||||
it { is_expected.to validate_presence_of(:key) }
|
||||
it { is_expected.to validate_length_of(:key).is_at_most(5000) }
|
||||
it { is_expected.to allow_value('ssh-foo').for(:key) }
|
||||
it { is_expected.to allow_value('ecdsa-foo').for(:key) }
|
||||
it { is_expected.to allow_value(attributes_for(:rsa_key_2048)[:key]).for(:key) }
|
||||
it { is_expected.to allow_value(attributes_for(:dsa_key_2048)[:key]).for(:key) }
|
||||
it { is_expected.to allow_value(attributes_for(:ecdsa_key_256)[:key]).for(:key) }
|
||||
it { is_expected.to allow_value(attributes_for(:ed25519_key_256)[:key]).for(:key) }
|
||||
it { is_expected.not_to allow_value('foo-bar').for(:key) }
|
||||
end
|
||||
|
||||
|
@ -95,6 +104,78 @@ describe Key, :mailer do
|
|||
end
|
||||
end
|
||||
|
||||
context 'validate it meets minimum bit length' do
|
||||
where(:factory, :minimum, :result) do
|
||||
[
|
||||
[:rsa_key_2048, 1024, true],
|
||||
[:rsa_key_2048, 2048, true],
|
||||
[:rsa_key_2048, 4096, false],
|
||||
[:dsa_key_2048, 1024, true],
|
||||
[:dsa_key_2048, 2048, true],
|
||||
[:dsa_key_2048, 4096, false],
|
||||
[:ecdsa_key_256, 256, true],
|
||||
[:ecdsa_key_256, 384, false],
|
||||
[:ed25519_key_256, 256, true],
|
||||
[:ed25519_key_256, 384, false]
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
subject(:key) { build(factory) }
|
||||
|
||||
before do
|
||||
stub_application_setting("minimum_#{key.public_key.type}_bits" => minimum)
|
||||
end
|
||||
|
||||
it { expect(key.valid?).to eq(result) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'validate the key type is allowed' do
|
||||
it 'accepts RSA, DSA, ECDSA and ED25519 keys by default' do
|
||||
expect(build(:rsa_key_2048)).to be_valid
|
||||
expect(build(:dsa_key_2048)).to be_valid
|
||||
expect(build(:ecdsa_key_256)).to be_valid
|
||||
expect(build(:ed25519_key_256)).to be_valid
|
||||
end
|
||||
|
||||
it 'rejects RSA, ECDSA and ED25519 keys if DSA is the only allowed type' do
|
||||
stub_application_setting(allowed_key_types: ['dsa'])
|
||||
|
||||
expect(build(:rsa_key_2048)).not_to be_valid
|
||||
expect(build(:dsa_key_2048)).to be_valid
|
||||
expect(build(:ecdsa_key_256)).not_to be_valid
|
||||
expect(build(:ed25519_key_256)).not_to be_valid
|
||||
end
|
||||
|
||||
it 'rejects RSA, DSA and ED25519 keys if ECDSA is the only allowed type' do
|
||||
stub_application_setting(allowed_key_types: ['ecdsa'])
|
||||
|
||||
expect(build(:rsa_key_2048)).not_to be_valid
|
||||
expect(build(:dsa_key_2048)).not_to be_valid
|
||||
expect(build(:ecdsa_key_256)).to be_valid
|
||||
expect(build(:ed25519_key_256)).not_to be_valid
|
||||
end
|
||||
|
||||
it 'rejects DSA, ECDSA and ED25519 keys if RSA is the only allowed type' do
|
||||
stub_application_setting(allowed_key_types: ['rsa'])
|
||||
|
||||
expect(build(:rsa_key_2048)).to be_valid
|
||||
expect(build(:dsa_key_2048)).not_to be_valid
|
||||
expect(build(:ecdsa_key_256)).not_to be_valid
|
||||
expect(build(:ed25519_key_256)).not_to be_valid
|
||||
end
|
||||
|
||||
it 'rejects RSA, DSA and ECDSA keys if ED25519 is the only allowed type' do
|
||||
stub_application_setting(allowed_key_types: ['ed25519'])
|
||||
|
||||
expect(build(:rsa_key_2048)).not_to be_valid
|
||||
expect(build(:dsa_key_2048)).not_to be_valid
|
||||
expect(build(:ecdsa_key_256)).not_to be_valid
|
||||
expect(build(:ed25519_key_256)).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context 'callbacks' do
|
||||
it 'adds new key to authorized_file' do
|
||||
key = build(:personal_key, id: 7)
|
||||
|
|
|
@ -19,6 +19,11 @@ describe API::Settings, 'Settings' do
|
|||
expect(json_response['default_project_visibility']).to be_a String
|
||||
expect(json_response['default_snippet_visibility']).to be_a String
|
||||
expect(json_response['default_group_visibility']).to be_a String
|
||||
expect(json_response['minimum_rsa_bits']).to eq(1024)
|
||||
expect(json_response['minimum_dsa_bits']).to eq(1024)
|
||||
expect(json_response['minimum_ecdsa_bits']).to eq(256)
|
||||
expect(json_response['minimum_ed25519_bits']).to eq(256)
|
||||
expect(json_response['allowed_key_types']).to contain_exactly('rsa', 'dsa', 'ecdsa', 'ed25519')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -44,7 +49,12 @@ describe API::Settings, 'Settings' do
|
|||
help_page_text: 'custom help text',
|
||||
help_page_hide_commercial_content: true,
|
||||
help_page_support_url: 'http://example.com/help',
|
||||
project_export_enabled: false
|
||||
project_export_enabled: false,
|
||||
minimum_rsa_bits: 2048,
|
||||
minimum_dsa_bits: 2048,
|
||||
minimum_ecdsa_bits: 384,
|
||||
minimum_ed25519_bits: 256,
|
||||
allowed_key_types: ['rsa']
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json_response['default_projects_limit']).to eq(3)
|
||||
|
@ -61,6 +71,11 @@ describe API::Settings, 'Settings' do
|
|||
expect(json_response['help_page_hide_commercial_content']).to be_truthy
|
||||
expect(json_response['help_page_support_url']).to eq('http://example.com/help')
|
||||
expect(json_response['project_export_enabled']).to be_falsey
|
||||
expect(json_response['minimum_rsa_bits']).to eq(2048)
|
||||
expect(json_response['minimum_dsa_bits']).to eq(2048)
|
||||
expect(json_response['minimum_ecdsa_bits']).to eq(384)
|
||||
expect(json_response['minimum_ed25519_bits']).to eq(256)
|
||||
expect(json_response['allowed_key_types']).to eq(['rsa'])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue