Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a89cb5cbdd
commit
6168721025
|
@ -0,0 +1,47 @@
|
|||
<script>
|
||||
import FileIcon from '~/vue_shared/components/file_icon.vue';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import { numberToHumanSize } from '~/lib/utils/number_utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FileIcon,
|
||||
ClipboardButton,
|
||||
},
|
||||
props: {
|
||||
blob: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
blobSize() {
|
||||
return numberToHumanSize(this.blob.size);
|
||||
},
|
||||
gfmCopyText() {
|
||||
return `\`${this.blob.path}\``;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="file-header-content d-flex align-items-center lh-100">
|
||||
<slot name="filepathPrepend"></slot>
|
||||
|
||||
<file-icon :file-name="blob.path" :size="18" aria-hidden="true" css-classes="mr-2" />
|
||||
<strong
|
||||
v-if="blob.name"
|
||||
class="file-title-name qa-file-title-name mr-1 js-blob-header-filepath"
|
||||
>{{ blob.name }}</strong
|
||||
>
|
||||
|
||||
<small class="mr-2">{{ blobSize }}</small>
|
||||
|
||||
<clipboard-button
|
||||
:text="blob.path"
|
||||
:gfm="gfmCopyText"
|
||||
:title="__('Copy file path')"
|
||||
css-class="btn-clipboard btn-transparent lh-100 position-static"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
|
@ -110,8 +110,8 @@ export const timeRanges = [
|
|||
duration: { seconds: 60 * 60 * 24 * 7 * 1 },
|
||||
},
|
||||
{
|
||||
label: __('2 weeks'),
|
||||
duration: { seconds: 60 * 60 * 24 * 7 * 2 },
|
||||
label: __('1 month'),
|
||||
duration: { seconds: 60 * 60 * 24 * 30 },
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -32,10 +32,6 @@
|
|||
|
||||
.snippet-file-content {
|
||||
border-radius: 3px;
|
||||
|
||||
.file-title-flex-parent .btn-clipboard {
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.snippet-header {
|
||||
|
|
|
@ -321,6 +321,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
.gpg-popover-certificate-details {
|
||||
ul {
|
||||
padding-left: $gl-padding;
|
||||
}
|
||||
|
||||
li.unstyled {
|
||||
list-style-type: none;
|
||||
}
|
||||
}
|
||||
|
||||
.gpg-popover-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
@ -25,7 +25,7 @@ class Commit
|
|||
attr_accessor :redacted_description_html
|
||||
attr_accessor :redacted_title_html
|
||||
attr_accessor :redacted_full_title_html
|
||||
attr_reader :gpg_commit, :container
|
||||
attr_reader :container
|
||||
|
||||
delegate :repository, to: :container
|
||||
delegate :project, to: :repository, allow_nil: true
|
||||
|
@ -123,7 +123,6 @@ class Commit
|
|||
|
||||
@raw = raw_commit
|
||||
@container = container
|
||||
@gpg_commit = Gitlab::Gpg::Commit.new(self) if container
|
||||
end
|
||||
|
||||
delegate \
|
||||
|
@ -320,13 +319,34 @@ class Commit
|
|||
)
|
||||
end
|
||||
|
||||
def signature
|
||||
return @signature if defined?(@signature)
|
||||
|
||||
@signature = gpg_commit.signature
|
||||
def has_signature?
|
||||
signature_type && signature_type != :NONE
|
||||
end
|
||||
|
||||
delegate :has_signature?, to: :gpg_commit
|
||||
def raw_signature_type
|
||||
strong_memoize(:raw_signature_type) do
|
||||
next unless @raw.instance_of?(Gitlab::Git::Commit)
|
||||
|
||||
@raw.raw_commit.signature_type if defined? @raw.raw_commit.signature_type
|
||||
end
|
||||
end
|
||||
|
||||
def signature_type
|
||||
@signature_type ||= raw_signature_type || :NONE
|
||||
end
|
||||
|
||||
def signature
|
||||
strong_memoize(:signature) do
|
||||
case signature_type
|
||||
when :PGP
|
||||
Gitlab::Gpg::Commit.new(self).signature
|
||||
when :X509
|
||||
Gitlab::X509::Commit.new(self).signature
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def revert_branch_name
|
||||
"revert-#{short_id}"
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module X509SerialNumberAttribute
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
def x509_serial_number_attribute(name)
|
||||
return if ENV['STATIC_VERIFICATION']
|
||||
|
||||
validate_binary_column_exists!(name) unless Rails.env.production?
|
||||
|
||||
attribute(name, Gitlab::Database::X509SerialNumberAttribute.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: x509_serial_number_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: x509_serial_number_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("x509_serial_number_attribute #{name.inspect} is invalid since the column type is not :binary")
|
||||
end
|
||||
rescue => error
|
||||
Gitlab::AppLogger.error "X509SerialNumberAttribute initialization: #{error.message}"
|
||||
raise
|
||||
end
|
||||
|
||||
def database_exists?
|
||||
Gitlab::Database.exists?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -190,6 +190,12 @@ class User < ApplicationRecord
|
|||
validate :owns_commit_email, if: :commit_email_changed?
|
||||
validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
|
||||
|
||||
validates :theme_id, allow_nil: true, inclusion: { in: Gitlab::Themes.valid_ids,
|
||||
message: _("%{placeholder} is not a valid theme") % { placeholder: '%{value}' } }
|
||||
|
||||
validates :color_scheme_id, allow_nil: true, inclusion: { in: Gitlab::ColorSchemes.valid_ids,
|
||||
message: _("%{placeholder} is not a valid color scheme") % { placeholder: '%{value}' } }
|
||||
|
||||
before_validation :sanitize_attrs
|
||||
before_validation :set_notification_email, if: :new_record?
|
||||
before_validation :set_public_email, if: :public_email_changed?
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class X509Certificate < ApplicationRecord
|
||||
include X509SerialNumberAttribute
|
||||
|
||||
x509_serial_number_attribute :serial_number
|
||||
|
||||
enum certificate_status: {
|
||||
good: 0,
|
||||
revoked: 1
|
||||
}
|
||||
|
||||
belongs_to :x509_issuer, class_name: 'X509Issuer', foreign_key: 'x509_issuer_id', optional: false
|
||||
|
||||
has_many :x509_commit_signatures, inverse_of: 'x509_certificate'
|
||||
|
||||
# rfc 5280 - 4.2.1.2 Subject Key Identifier
|
||||
validates :subject_key_identifier, presence: true, format: { with: /\A(\h{2}:){19}\h{2}\z/ }
|
||||
# rfc 5280 - 4.1.2.6 Subject
|
||||
validates :subject, presence: true
|
||||
# rfc 5280 - 4.1.2.6 Subject (subjectAltName contains the email address)
|
||||
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
||||
# rfc 5280 - 4.1.2.2 Serial number
|
||||
validates :serial_number, presence: true, numericality: { only_integer: true }
|
||||
|
||||
validates :x509_issuer_id, presence: true
|
||||
|
||||
def self.safe_create!(attributes)
|
||||
create_with(attributes)
|
||||
.safe_find_or_create_by!(subject_key_identifier: attributes[:subject_key_identifier])
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class X509CommitSignature < ApplicationRecord
|
||||
include ShaAttribute
|
||||
|
||||
sha_attribute :commit_sha
|
||||
|
||||
enum verification_status: {
|
||||
unverified: 0,
|
||||
verified: 1
|
||||
}
|
||||
|
||||
belongs_to :project, class_name: 'Project', foreign_key: 'project_id', optional: false
|
||||
belongs_to :x509_certificate, class_name: 'X509Certificate', foreign_key: 'x509_certificate_id', optional: false
|
||||
|
||||
validates :commit_sha, presence: true
|
||||
validates :project_id, presence: true
|
||||
validates :x509_certificate_id, presence: true
|
||||
|
||||
scope :by_commit_sha, ->(shas) { where(commit_sha: shas) }
|
||||
|
||||
def self.safe_create!(attributes)
|
||||
create_with(attributes)
|
||||
.safe_find_or_create_by!(commit_sha: attributes[:commit_sha])
|
||||
end
|
||||
|
||||
# Find commits that are lacking a signature in the database at present
|
||||
def self.unsigned_commit_shas(commit_shas)
|
||||
return [] if commit_shas.empty?
|
||||
|
||||
signed = by_commit_sha(commit_shas).pluck(:commit_sha)
|
||||
commit_shas - signed
|
||||
end
|
||||
|
||||
def commit
|
||||
project.commit(commit_sha)
|
||||
end
|
||||
|
||||
def x509_commit
|
||||
return unless commit
|
||||
|
||||
Gitlab::X509::Commit.new(commit)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class X509Issuer < ApplicationRecord
|
||||
has_many :x509_certificates, inverse_of: 'x509_issuer'
|
||||
|
||||
# rfc 5280 - 4.2.1.1 Authority Key Identifier
|
||||
validates :subject_key_identifier, presence: true, format: { with: /\A(\h{2}:){19}\h{2}\z/ }
|
||||
# rfc 5280 - 4.1.2.4 Issuer
|
||||
validates :subject, presence: true
|
||||
# rfc 5280 - 4.2.1.14 CRL Distribution Points
|
||||
# cRLDistributionPoints extension using URI:http
|
||||
validates :crl_url, presence: true, public_url: true
|
||||
|
||||
def self.safe_create!(attributes)
|
||||
create_with(attributes)
|
||||
.safe_find_or_create_by!(subject_key_identifier: attributes[:subject_key_identifier])
|
||||
end
|
||||
end
|
|
@ -6,7 +6,7 @@ module Git
|
|||
execute_branch_hooks
|
||||
|
||||
super.tap do
|
||||
enqueue_update_gpg_signatures
|
||||
enqueue_update_signatures
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -103,14 +103,22 @@ module Git
|
|||
end
|
||||
end
|
||||
|
||||
def enqueue_update_gpg_signatures
|
||||
unsigned = GpgSignature.unsigned_commit_shas(limited_commits.map(&:sha))
|
||||
def unsigned_x509_shas(commits)
|
||||
X509CommitSignature.unsigned_commit_shas(commits.map(&:sha))
|
||||
end
|
||||
|
||||
def unsigned_gpg_shas(commits)
|
||||
GpgSignature.unsigned_commit_shas(commits.map(&:sha))
|
||||
end
|
||||
|
||||
def enqueue_update_signatures
|
||||
unsigned = unsigned_x509_shas(commits) & unsigned_gpg_shas(commits)
|
||||
return if unsigned.empty?
|
||||
|
||||
signable = Gitlab::Git::Commit.shas_with_signatures(project.repository, unsigned)
|
||||
return if signable.empty?
|
||||
|
||||
CreateGpgSignatureWorker.perform_async(signable, project.id)
|
||||
CreateCommitSignatureWorker.perform_async(signable, project.id)
|
||||
end
|
||||
|
||||
# It's not sufficient to just check for a blank SHA as it's possible for the
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
- if signature
|
||||
= render partial: "projects/commit/#{signature.verification_status}_signature_badge", locals: { signature: signature }
|
||||
- uri = "projects/commit/#{"x509/" if signature.instance_of?(X509CommitSignature)}"
|
||||
= render partial: "#{uri}#{signature.verification_status}_signature_badge", locals: { signature: signature }
|
||||
|
|
|
@ -17,12 +17,18 @@
|
|||
- content = capture do
|
||||
- if show_user
|
||||
.clearfix
|
||||
= render partial: 'projects/commit/signature_badge_user', locals: { signature: signature }
|
||||
- uri_signature_badge_user = "projects/commit/#{"x509/" if signature.instance_of?(X509CommitSignature)}signature_badge_user"
|
||||
= render partial: "#{uri_signature_badge_user}", locals: { signature: signature }
|
||||
|
||||
= _('GPG Key ID:')
|
||||
%span.monospace= signature.gpg_key_primary_keyid
|
||||
- if signature.instance_of?(X509CommitSignature)
|
||||
= render partial: "projects/commit/x509/certificate_details", locals: { signature: signature }
|
||||
|
||||
= link_to(_('Learn more about signing commits'), help_page_path('user/project/repository/gpg_signed_commits/index.md'), class: 'gpg-popover-help-link')
|
||||
= link_to(_('Learn more about x509 signed commits'), help_page_path('user/project/repository/x509_signed_commits/index.md'), class: 'gpg-popover-help-link')
|
||||
- else
|
||||
= _('GPG Key ID:')
|
||||
%span.monospace= signature.gpg_key_primary_keyid
|
||||
|
||||
= link_to(_('Learn more about signing commits'), help_page_path('user/project/repository/gpg_signed_commits/index.md'), class: 'gpg-popover-help-link')
|
||||
|
||||
%button{ tabindex: 0, class: css_classes, data: { toggle: 'popover', html: 'true', placement: 'top', title: title, content: content } }
|
||||
= label
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
.gpg-popover-certificate-details
|
||||
%strong= _('Certificate Subject')
|
||||
%ul
|
||||
- signature.x509_certificate.subject.split(",").each do |i|
|
||||
- if i.start_with?("CN", "O")
|
||||
%li= i
|
||||
%li= _('Subject Key Identifier:')
|
||||
%li.unstyled= signature.x509_certificate.subject_key_identifier.gsub(":", " ")
|
||||
|
||||
.gpg-popover-certificate-details
|
||||
%strong= _('Certificate Issuer')
|
||||
%ul
|
||||
- signature.x509_certificate.x509_issuer.subject.split(",").each do |i|
|
||||
- if i.start_with?("CN", "OU", "O")
|
||||
%li= i
|
||||
%li= _('Subject Key Identifier:')
|
||||
%li.unstyled= signature.x509_certificate.x509_issuer.subject_key_identifier.gsub(":", " ")
|
|
@ -0,0 +1,19 @@
|
|||
- user = signature.commit.committer
|
||||
- user_email = signature.x509_certificate.email
|
||||
|
||||
- if user
|
||||
= link_to user_path(user), class: 'gpg-popover-user-link' do
|
||||
%div
|
||||
= user_avatar_without_link(user: user, size: 32)
|
||||
|
||||
%div
|
||||
%strong= user.name
|
||||
%div= user.to_reference
|
||||
|
||||
- else
|
||||
= mail_to user_email do
|
||||
%div
|
||||
= user_avatar_without_link(user_email: user_email, size: 32)
|
||||
|
||||
%div
|
||||
%strong= user_email
|
|
@ -0,0 +1,6 @@
|
|||
- title = capture do
|
||||
= _('This commit was signed with an <strong>unverified</strong> signature.').html_safe
|
||||
|
||||
- locals = { signature: signature, title: title, label: _('Unverified'), css_class: 'invalid', icon: 'status_notfound_borderless', show_user: true }
|
||||
|
||||
= render partial: 'projects/commit/signature_badge', locals: locals
|
|
@ -0,0 +1,6 @@
|
|||
- title = capture do
|
||||
= _('This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user.').html_safe
|
||||
|
||||
- locals = { signature: signature, title: title, label: _('Verified'), css_class: 'valid', icon: 'status_success_borderless', show_user: true }
|
||||
|
||||
= render partial: 'projects/commit/signature_badge', locals: locals
|
|
@ -699,14 +699,14 @@
|
|||
:latency_sensitive: true
|
||||
:resource_boundary: :unknown
|
||||
:weight: 2
|
||||
- :name: create_evidence
|
||||
:feature_category: :release_governance
|
||||
- :name: create_commit_signature
|
||||
:feature_category: :source_code_management
|
||||
:has_external_dependencies:
|
||||
:latency_sensitive:
|
||||
:resource_boundary: :unknown
|
||||
:weight: 2
|
||||
- :name: create_gpg_signature
|
||||
:feature_category: :source_code_management
|
||||
- :name: create_evidence
|
||||
:feature_category: :release_governance
|
||||
:has_external_dependencies:
|
||||
:latency_sensitive:
|
||||
:resource_boundary: :unknown
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateGpgSignatureWorker
|
||||
class CreateCommitSignatureWorker
|
||||
include ApplicationWorker
|
||||
|
||||
feature_category :source_code_management
|
||||
|
@ -23,7 +23,12 @@ class CreateGpgSignatureWorker
|
|||
|
||||
# This calculates and caches the signature in the database
|
||||
commits.each do |commit|
|
||||
Gitlab::Gpg::Commit.new(commit).signature
|
||||
case commit.signature_type
|
||||
when :PGP
|
||||
Gitlab::Gpg::Commit.new(commit).signature
|
||||
when :X509
|
||||
Gitlab::X509::Commit.new(commit).signature
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error("Failed to create signature for commit #{commit.id}. Error: #{e.message}") # rubocop:disable Gitlab/RailsLogger
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Extend logs retention to period from 15 to 30 days
|
||||
merge_request: 24466
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Expose theme and color scheme user preferences in API
|
||||
merge_request: 24409
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: x509 signed commits using openssl
|
||||
merge_request: 17773
|
||||
author: Roger Meier
|
||||
type: added
|
|
@ -38,6 +38,7 @@
|
|||
- dependency_scanning
|
||||
- design_management
|
||||
- devops_score
|
||||
- digital_experience_management
|
||||
- disaster_recovery
|
||||
- dynamic_application_security_testing
|
||||
- epics
|
||||
|
@ -96,7 +97,6 @@
|
|||
- static_site_editor
|
||||
- status_page
|
||||
- subgroups
|
||||
- synthetic_monitoring
|
||||
- system_testing
|
||||
- teams
|
||||
- templates
|
||||
|
|
|
@ -42,12 +42,12 @@
|
|||
- 2
|
||||
- - container_repository
|
||||
- 1
|
||||
- - create_commit_signature
|
||||
- 2
|
||||
- - create_evidence
|
||||
- 2
|
||||
- - create_github_webhook
|
||||
- 2
|
||||
- - create_gpg_signature
|
||||
- 2
|
||||
- - create_note_diff_file
|
||||
- 1
|
||||
- - cronjob
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class CreateX509Signatures < ActiveRecord::Migration[5.2]
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
create_table :x509_issuers do |t|
|
||||
t.timestamps_with_timezone null: false
|
||||
|
||||
t.string :subject_key_identifier, index: true, null: false, unique: true, limit: 255
|
||||
t.string :subject, null: false, limit: 255
|
||||
t.string :crl_url, null: false, limit: 255
|
||||
end
|
||||
|
||||
create_table :x509_certificates do |t|
|
||||
t.timestamps_with_timezone null: false
|
||||
|
||||
t.string :subject_key_identifier, index: true, null: false, unique: true, limit: 255
|
||||
t.string :subject, null: false, limit: 255
|
||||
t.string :email, null: false, limit: 255
|
||||
t.binary :serial_number, null: false
|
||||
|
||||
t.integer :certificate_status, limit: 2, default: 0, null: false
|
||||
|
||||
t.references :x509_issuer, index: true, null: false, foreign_key: { on_delete: :cascade }
|
||||
end
|
||||
|
||||
create_table :x509_commit_signatures do |t|
|
||||
t.timestamps_with_timezone null: false
|
||||
|
||||
t.references :project, index: true, null: false, foreign_key: { on_delete: :cascade }
|
||||
t.references :x509_certificate, index: true, null: false, foreign_key: { on_delete: :cascade }
|
||||
|
||||
t.binary :commit_sha, index: true, null: false
|
||||
t.integer :verification_status, limit: 2, default: 0, null: false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MigrateCreateCommitSignatureWorkerSidekiqQueue < ActiveRecord::Migration[5.2]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
def up
|
||||
sidekiq_queue_migrate 'create_gpg_signature', to: 'create_commit_signature'
|
||||
end
|
||||
|
||||
def down
|
||||
sidekiq_queue_migrate 'create_commit_signature', to: 'create_gpg_signature'
|
||||
end
|
||||
end
|
39
db/schema.rb
39
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: 2020_02_05_143231) do
|
||||
ActiveRecord::Schema.define(version: 2020_02_06_091544) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pg_trgm"
|
||||
|
@ -4482,6 +4482,40 @@ ActiveRecord::Schema.define(version: 2020_02_05_143231) do
|
|||
t.index ["type"], name: "index_web_hooks_on_type"
|
||||
end
|
||||
|
||||
create_table "x509_certificates", force: :cascade do |t|
|
||||
t.datetime_with_timezone "created_at", null: false
|
||||
t.datetime_with_timezone "updated_at", null: false
|
||||
t.string "subject_key_identifier", limit: 255, null: false
|
||||
t.string "subject", limit: 255, null: false
|
||||
t.string "email", limit: 255, null: false
|
||||
t.binary "serial_number", null: false
|
||||
t.integer "certificate_status", limit: 2, default: 0, null: false
|
||||
t.bigint "x509_issuer_id", null: false
|
||||
t.index ["subject_key_identifier"], name: "index_x509_certificates_on_subject_key_identifier"
|
||||
t.index ["x509_issuer_id"], name: "index_x509_certificates_on_x509_issuer_id"
|
||||
end
|
||||
|
||||
create_table "x509_commit_signatures", force: :cascade do |t|
|
||||
t.datetime_with_timezone "created_at", null: false
|
||||
t.datetime_with_timezone "updated_at", null: false
|
||||
t.bigint "project_id", null: false
|
||||
t.bigint "x509_certificate_id", null: false
|
||||
t.binary "commit_sha", null: false
|
||||
t.integer "verification_status", limit: 2, default: 0, null: false
|
||||
t.index ["commit_sha"], name: "index_x509_commit_signatures_on_commit_sha"
|
||||
t.index ["project_id"], name: "index_x509_commit_signatures_on_project_id"
|
||||
t.index ["x509_certificate_id"], name: "index_x509_commit_signatures_on_x509_certificate_id"
|
||||
end
|
||||
|
||||
create_table "x509_issuers", force: :cascade do |t|
|
||||
t.datetime_with_timezone "created_at", null: false
|
||||
t.datetime_with_timezone "updated_at", null: false
|
||||
t.string "subject_key_identifier", limit: 255, null: false
|
||||
t.string "subject", limit: 255, null: false
|
||||
t.string "crl_url", limit: 255, null: false
|
||||
t.index ["subject_key_identifier"], name: "index_x509_issuers_on_subject_key_identifier"
|
||||
end
|
||||
|
||||
create_table "zoom_meetings", force: :cascade do |t|
|
||||
t.bigint "project_id", null: false
|
||||
t.bigint "issue_id", null: false
|
||||
|
@ -4973,6 +5007,9 @@ ActiveRecord::Schema.define(version: 2020_02_05_143231) do
|
|||
add_foreign_key "vulnerability_scanners", "projects", on_delete: :cascade
|
||||
add_foreign_key "web_hook_logs", "web_hooks", on_delete: :cascade
|
||||
add_foreign_key "web_hooks", "projects", name: "fk_0c8ca6d9d1", on_delete: :cascade
|
||||
add_foreign_key "x509_certificates", "x509_issuers", on_delete: :cascade
|
||||
add_foreign_key "x509_commit_signatures", "projects", on_delete: :cascade
|
||||
add_foreign_key "x509_commit_signatures", "x509_certificates", on_delete: :cascade
|
||||
add_foreign_key "zoom_meetings", "issues", on_delete: :cascade
|
||||
add_foreign_key "zoom_meetings", "projects", on_delete: :cascade
|
||||
end
|
||||
|
|
|
@ -385,6 +385,8 @@ Parameters:
|
|||
- `skip_confirmation` (optional) - Skip confirmation - true or false (default)
|
||||
- `external` (optional) - Flags the user as external - true or false (default)
|
||||
- `avatar` (optional) - Image file for user's avatar
|
||||
- `theme_id` (optional) - The GitLab theme for the user (see [the user preference docs](../user/profile/preferences.md#navigation-theme) for more information)
|
||||
- `color_scheme_id` (optional) - User's color scheme for the file viewer (see [the user preference docs](../user/profile/preferences.md#syntax-highlighting-theme) for more information)
|
||||
- `private_profile` (optional) - User's profile is private - true, false (default), or null (will be converted to false)
|
||||
- `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user **(STARTER)**
|
||||
- `extra_shared_runners_minutes_limit` (optional) - Extra pipeline minutes quota for this user **(STARTER)**
|
||||
|
@ -423,6 +425,8 @@ Parameters:
|
|||
- `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user
|
||||
- `extra_shared_runners_minutes_limit` (optional) - Extra pipeline minutes quota for this user
|
||||
- `avatar` (optional) - Image file for user's avatar
|
||||
- `theme_id` (optional) - The GitLab theme for the user (see [the user preference docs](../user/profile/preferences.md#navigation-theme) for more information)
|
||||
- `color_scheme_id` (optional) - User's color scheme for the file viewer (see [the user preference docs](../user/profile/preferences.md#syntax-highlighting-theme) for more information)
|
||||
- `private_profile` (optional) - User's profile is private - true, false (default), or null (will be converted to false)
|
||||
- `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user **(STARTER)**
|
||||
- `extra_shared_runners_minutes_limit` (optional) - Extra pipeline minutes quota for this user **(STARTER)**
|
||||
|
|
|
@ -30,7 +30,7 @@ Our codebase style is defined and enforced by [RuboCop](https://github.com/ruboc
|
|||
You can check for any offenses locally with `bundle exec rubocop --parallel`.
|
||||
On the CI, this is automatically checked by the `static-analysis` jobs.
|
||||
|
||||
For RuboCop rules that we have not taken a decision yet, we follow the
|
||||
For RuboCop rules that we have not taken a decision on yet, we follow the
|
||||
[Ruby Style Guide](https://github.com/rubocop-hq/ruby-style-guide),
|
||||
[Rails Style Guide](https://github.com/rubocop-hq/rails-style-guide), and
|
||||
[RSpec Style Guide](https://github.com/rubocop-hq/rspec-style-guide) as general
|
||||
|
|
|
@ -437,7 +437,7 @@ Filebeat will run as a DaemonSet on each node in your cluster, and it will ship
|
|||
GitLab will then connect to Elasticsearch for logs instead of the Kubernetes API,
|
||||
and you will have access to more advanced querying capabilities.
|
||||
|
||||
Log data is automatically deleted after 15 days using [Curator](https://www.elastic.co/guide/en/elasticsearch/client/curator/5.5/about.html).
|
||||
Log data is automatically deleted after 30 days using [Curator](https://www.elastic.co/guide/en/elasticsearch/client/curator/5.5/about.html).
|
||||
|
||||
To enable log shipping, install Elastic Stack into the cluster with the **Install** button.
|
||||
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
---
|
||||
type: concepts, howto
|
||||
---
|
||||
|
||||
# Signing commits with x509
|
||||
|
||||
[x509](https://en.wikipedia.org/wiki/X.509) is a standard format for public key
|
||||
certificates issued by a public or private Public Key Infrastructure (PKI).
|
||||
Personal x509 certificates are used for authentication or signing purposes
|
||||
such as SMIME, but beside that, Git supports signing of commits and tags
|
||||
with x509 certificates in a similar way as with [GPG](../gpg_signed_commits/index.md).
|
||||
The main difference is the trust anchor which is the PKI for x509 certificates
|
||||
instead of a web of trust with GPG.
|
||||
|
||||
## How GitLab handles x509
|
||||
|
||||
GitLab uses its own certificate store and therefore defines the trust chain.
|
||||
|
||||
For a commit to be *verified* by GitLab:
|
||||
|
||||
- The signing certificate email must match a verified email address used by the committer in GitLab.
|
||||
- The Certificate Authority has to be trusted by the GitLab instance, see also
|
||||
[Omnibus install custom public certificates](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
|
||||
- The signing time has to be within the time range of the [certificate validity](https://www.rfc-editor.org/rfc/rfc5280.html#section-4.1.2.5)
|
||||
which is usually up to three years.
|
||||
- The signing time is equal or later then commit time.
|
||||
|
||||
NOTE: **Note:** There is no certificate revocation list check in place at the moment.
|
||||
|
||||
## Obtaining an x509 key pair
|
||||
|
||||
If your organization has Public Key Infrastructure (PKI), that PKI will provide
|
||||
an S/MIME key.
|
||||
|
||||
If you do not have an S/MIME key pair from a PKI, you can either create your
|
||||
own self-signed one, or purchase one. MozillaZine keeps a nice collection
|
||||
of [S/MIME-capable signing authorities](http://kb.mozillazine.org/Getting_an_SMIME_certificate)
|
||||
and some of them generate keys for free.
|
||||
|
||||
## Associating your x509 certificate with Git
|
||||
|
||||
To take advantage of X509 signing, you will need Git 2.19.0 or later. You can
|
||||
check your Git version with:
|
||||
|
||||
```sh
|
||||
git --version
|
||||
```
|
||||
|
||||
If you have the correct version, you can proceed to configure Git.
|
||||
|
||||
### Linux
|
||||
|
||||
Configure Git to use your key for signing:
|
||||
|
||||
```sh
|
||||
signingkey = $( gpgsm --list-secret-keys | egrep '(key usage|ID)' | grep -B 1 digitalSignature | awk '/ID/ {print $2}' )
|
||||
git config --global user.signingkey $signingkey
|
||||
git config --global gpg.format x509
|
||||
```
|
||||
|
||||
### Windows and MacOS
|
||||
|
||||
Install [smimesign](https://github.com/github/smimesign) by downloading the
|
||||
installer or via `brew install smimesign` on MacOS.
|
||||
|
||||
Get the ID of your certificate with `smimesign --list-keys` and set your
|
||||
signingkey `git config --global user.signingkey ID`, then configure x509:
|
||||
|
||||
```sh
|
||||
git config --global gpg.x509.program smimesign
|
||||
git config --global gpg.format x509
|
||||
```
|
||||
|
||||
## Signing commits
|
||||
|
||||
After you have [associated your x509 certificate with Git](#associating-your-x509-certificate-with-git) you
|
||||
can start signing your commits:
|
||||
|
||||
1. Commit like you used to, the only difference is the addition of the `-S` flag:
|
||||
|
||||
```sh
|
||||
git commit -S -m "feat: x509 signed commits"
|
||||
```
|
||||
|
||||
1. Push to GitLab and check that your commits [are verified](#verifying-commits).
|
||||
|
||||
If you don't want to type the `-S` flag every time you commit, you can tell Git
|
||||
to sign your commits automatically:
|
||||
|
||||
```sh
|
||||
git config --global commit.gpgsign true
|
||||
```
|
||||
|
||||
## Verifying commits
|
||||
|
||||
To verify that a commit is signed, you can use the `--show-signature` flag:
|
||||
|
||||
```sh
|
||||
git log --show-signature
|
||||
```
|
|
@ -52,6 +52,8 @@ module API
|
|||
optional :external, type: Boolean, desc: 'Flag indicating the user is an external user'
|
||||
# TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960
|
||||
optional :avatar, type: File, desc: 'Avatar image for user' # rubocop:disable Scalability/FileUploads
|
||||
optional :theme_id, type: Integer, default: 1, desc: 'The GitLab theme for the user'
|
||||
optional :color_scheme_id, type: Integer, default: 1, desc: 'The color scheme for the file viewer'
|
||||
optional :private_profile, type: Boolean, desc: 'Flag indicating the user has a private profile'
|
||||
all_or_none_of :extern_uid, :provider
|
||||
|
||||
|
|
|
@ -66,5 +66,9 @@ module Gitlab
|
|||
default
|
||||
end
|
||||
end
|
||||
|
||||
def self.valid_ids
|
||||
SCHEMES.map(&:id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Database
|
||||
# Class for casting binary data to int.
|
||||
#
|
||||
# Using X509SerialNumberAttribute allows you to store X509 certificate
|
||||
# serial number values as binary while still using integer to access them.
|
||||
# rfc 5280 - 4.1.2.2 Serial number (20 octets is the maximum), could be:
|
||||
# - 1461501637330902918203684832716283019655932542975
|
||||
# - 0xffffffffffffffffffffffffffffffffffffffff
|
||||
class X509SerialNumberAttribute < ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bytea
|
||||
PACK_FORMAT = 'H*'
|
||||
|
||||
def deserialize(value)
|
||||
value = super(value)
|
||||
value ? value.unpack1(PACK_FORMAT).to_i : nil
|
||||
end
|
||||
|
||||
def serialize(value)
|
||||
arg = value ? [value.to_s].pack(PACK_FORMAT) : nil
|
||||
super(arg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,36 +2,9 @@
|
|||
|
||||
module Gitlab
|
||||
module Gpg
|
||||
class Commit
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
def initialize(commit)
|
||||
@commit = commit
|
||||
|
||||
repo = commit.container.repository.raw_repository
|
||||
@signature_data = Gitlab::Git::Commit.extract_signature_lazily(repo, commit.sha || commit.id)
|
||||
|
||||
lazy_signature
|
||||
end
|
||||
|
||||
def signature_text
|
||||
strong_memoize(:signature_text) do
|
||||
@signature_data&.itself && @signature_data[0] # rubocop:disable Lint/SafeNavigationConsistency
|
||||
end
|
||||
end
|
||||
|
||||
def signed_text
|
||||
strong_memoize(:signed_text) do
|
||||
@signature_data&.itself && @signature_data[1] # rubocop:disable Lint/SafeNavigationConsistency
|
||||
end
|
||||
end
|
||||
|
||||
def has_signature?
|
||||
!!(signature_text && signed_text)
|
||||
end
|
||||
|
||||
class Commit < Gitlab::SignedCommit
|
||||
def signature
|
||||
return unless has_signature?
|
||||
super
|
||||
|
||||
return @signature if @signature
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
class SignedCommit
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
def initialize(commit)
|
||||
@commit = commit
|
||||
|
||||
if commit.project
|
||||
repo = commit.project.repository.raw_repository
|
||||
@signature_data = Gitlab::Git::Commit.extract_signature_lazily(repo, commit.sha || commit.id)
|
||||
end
|
||||
|
||||
lazy_signature
|
||||
end
|
||||
|
||||
def signature
|
||||
return unless @commit.has_signature?
|
||||
end
|
||||
|
||||
def signature_text
|
||||
strong_memoize(:signature_text) do
|
||||
@signature_data.itself ? @signature_data[0] : nil
|
||||
end
|
||||
end
|
||||
|
||||
def signed_text
|
||||
strong_memoize(:signed_text) do
|
||||
@signature_data.itself ? @signature_data[1] : nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -77,6 +77,10 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def self.valid_ids
|
||||
THEMES.map(&:id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_id
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
# frozen_string_literal: true
|
||||
require 'openssl'
|
||||
require 'digest'
|
||||
|
||||
module Gitlab
|
||||
module X509
|
||||
class Commit < Gitlab::SignedCommit
|
||||
def signature
|
||||
super
|
||||
|
||||
return @signature if @signature
|
||||
|
||||
cached_signature = lazy_signature&.itself
|
||||
return @signature = cached_signature if cached_signature.present?
|
||||
|
||||
@signature = create_cached_signature!
|
||||
end
|
||||
|
||||
def update_signature!(cached_signature)
|
||||
cached_signature.update!(attributes)
|
||||
@signature = cached_signature
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def lazy_signature
|
||||
BatchLoader.for(@commit.sha).batch do |shas, loader|
|
||||
X509CommitSignature.by_commit_sha(shas).each do |signature|
|
||||
loader.call(signature.commit_sha, signature)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def verified_signature
|
||||
strong_memoize(:verified_signature) { verified_signature? }
|
||||
end
|
||||
|
||||
def cert
|
||||
strong_memoize(:cert) do
|
||||
signer_certificate(p7) if valid_signature?
|
||||
end
|
||||
end
|
||||
|
||||
def cert_store
|
||||
strong_memoize(:cert_store) do
|
||||
store = OpenSSL::X509::Store.new
|
||||
store.set_default_paths
|
||||
# valid_signing_time? checks the time attributes already
|
||||
# this flag is required, otherwise expired certificates would become
|
||||
# unverified when notAfter within certificate attribute is reached
|
||||
store.flags = OpenSSL::X509::V_FLAG_NO_CHECK_TIME
|
||||
store
|
||||
end
|
||||
end
|
||||
|
||||
def p7
|
||||
strong_memoize(:p7) do
|
||||
pkcs7_text = signature_text.sub('-----BEGIN SIGNED MESSAGE-----', '-----BEGIN PKCS7-----')
|
||||
pkcs7_text = pkcs7_text.sub('-----END SIGNED MESSAGE-----', '-----END PKCS7-----')
|
||||
|
||||
OpenSSL::PKCS7.new(pkcs7_text)
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def valid_signing_time?
|
||||
# rfc 5280 - 4.1.2.5 Validity
|
||||
# check if signed_time is within the time range (notBefore/notAfter)
|
||||
# non-rfc - git specific check: signed_time >= commit_time
|
||||
p7.signers[0].signed_time.between?(cert.not_before, cert.not_after) &&
|
||||
p7.signers[0].signed_time >= @commit.created_at
|
||||
end
|
||||
|
||||
def valid_signature?
|
||||
p7.verify([], cert_store, signed_text, OpenSSL::PKCS7::NOVERIFY)
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
def verified_signature?
|
||||
# verify has multiple options but only a boolean return value
|
||||
# so first verify without certificate chain
|
||||
if valid_signature?
|
||||
if valid_signing_time?
|
||||
# verify with system certificate chain
|
||||
p7.verify([], cert_store, signed_text)
|
||||
else
|
||||
false
|
||||
end
|
||||
else
|
||||
nil
|
||||
end
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
def signer_certificate(p7)
|
||||
p7.certificates.each do |cert|
|
||||
next if cert.serial != p7.signers[0].serial
|
||||
|
||||
return cert
|
||||
end
|
||||
end
|
||||
|
||||
def certificate_crl
|
||||
extension = get_certificate_extension('crlDistributionPoints')
|
||||
extension.split('URI:').each do |item|
|
||||
item.strip
|
||||
|
||||
if item.start_with?("http")
|
||||
return item.strip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_certificate_extension(extension)
|
||||
cert.extensions.each do |ext|
|
||||
if ext.oid == extension
|
||||
return ext.value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def issuer_subject_key_identifier
|
||||
get_certificate_extension('authorityKeyIdentifier').gsub("keyid:", "").delete!("\n")
|
||||
end
|
||||
|
||||
def certificate_subject_key_identifier
|
||||
get_certificate_extension('subjectKeyIdentifier')
|
||||
end
|
||||
|
||||
def certificate_issuer
|
||||
cert.issuer.to_s(OpenSSL::X509::Name::RFC2253)
|
||||
end
|
||||
|
||||
def certificate_subject
|
||||
cert.subject.to_s(OpenSSL::X509::Name::RFC2253)
|
||||
end
|
||||
|
||||
def certificate_email
|
||||
get_certificate_extension('subjectAltName').split('email:')[1]
|
||||
end
|
||||
|
||||
def issuer_attributes
|
||||
return if verified_signature.nil?
|
||||
|
||||
{
|
||||
subject_key_identifier: issuer_subject_key_identifier,
|
||||
subject: certificate_issuer,
|
||||
crl_url: certificate_crl
|
||||
}
|
||||
end
|
||||
|
||||
def certificate_attributes
|
||||
return if verified_signature.nil?
|
||||
|
||||
issuer = X509Issuer.safe_create!(issuer_attributes)
|
||||
|
||||
{
|
||||
subject_key_identifier: certificate_subject_key_identifier,
|
||||
subject: certificate_subject,
|
||||
email: certificate_email,
|
||||
serial_number: cert.serial,
|
||||
x509_issuer_id: issuer.id
|
||||
}
|
||||
end
|
||||
|
||||
def attributes
|
||||
return if verified_signature.nil?
|
||||
|
||||
certificate = X509Certificate.safe_create!(certificate_attributes)
|
||||
|
||||
{
|
||||
commit_sha: @commit.sha,
|
||||
project: @commit.project,
|
||||
x509_certificate_id: certificate.id,
|
||||
verification_status: verification_status
|
||||
}
|
||||
end
|
||||
|
||||
def verification_status
|
||||
if verified_signature && certificate_email == @commit.committer_email
|
||||
:verified
|
||||
else
|
||||
:unverified
|
||||
end
|
||||
end
|
||||
|
||||
def create_cached_signature!
|
||||
return if verified_signature.nil?
|
||||
|
||||
return X509CommitSignature.new(attributes) if Gitlab::Database.read_only?
|
||||
|
||||
X509CommitSignature.safe_create!(attributes)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -361,6 +361,12 @@ msgstr ""
|
|||
msgid "%{percent}%{percentSymbol} complete"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{placeholder} is not a valid color scheme"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{placeholder} is not a valid theme"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{primary} (%{secondary})"
|
||||
msgstr ""
|
||||
|
||||
|
@ -618,6 +624,9 @@ msgid_plural "%d minutes"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "1 month"
|
||||
msgstr ""
|
||||
|
||||
msgid "1 open issue"
|
||||
msgid_plural "%{issues} open issues"
|
||||
msgstr[0] ""
|
||||
|
@ -655,9 +664,6 @@ msgstr ""
|
|||
msgid "1st contribution!"
|
||||
msgstr ""
|
||||
|
||||
msgid "2 weeks"
|
||||
msgstr ""
|
||||
|
||||
msgid "20-29 contributions"
|
||||
msgstr ""
|
||||
|
||||
|
@ -3188,6 +3194,12 @@ msgstr ""
|
|||
msgid "Certificate (PEM)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Certificate Issuer"
|
||||
msgstr ""
|
||||
|
||||
msgid "Certificate Subject"
|
||||
msgstr ""
|
||||
|
||||
msgid "Change assignee"
|
||||
msgstr ""
|
||||
|
||||
|
@ -11124,6 +11136,9 @@ msgstr ""
|
|||
msgid "Learn more about the dependency list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Learn more about x509 signed commits"
|
||||
msgstr ""
|
||||
|
||||
msgid "Learn more in the"
|
||||
msgstr ""
|
||||
|
||||
|
@ -18167,6 +18182,9 @@ msgstr ""
|
|||
msgid "Subgroups and projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "Subject Key Identifier:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Subkeys"
|
||||
msgstr ""
|
||||
|
||||
|
|
3
qa/qa.rb
3
qa/qa.rb
|
@ -16,6 +16,8 @@ module QA
|
|||
module Flow
|
||||
autoload :Login, 'qa/flow/login'
|
||||
autoload :Project, 'qa/flow/project'
|
||||
autoload :Saml, 'qa/flow/saml'
|
||||
autoload :User, 'qa/flow/user'
|
||||
end
|
||||
|
||||
##
|
||||
|
@ -431,6 +433,7 @@ module QA
|
|||
autoload :NodeJs, 'qa/service/docker_run/node_js'
|
||||
autoload :GitlabRunner, 'qa/service/docker_run/gitlab_runner'
|
||||
autoload :MailHog, 'qa/service/docker_run/mail_hog'
|
||||
autoload :SamlIdp, 'qa/service/docker_run/saml_idp'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Flow
|
||||
module Saml
|
||||
module_function
|
||||
|
||||
def page
|
||||
Capybara.current_session
|
||||
end
|
||||
|
||||
def logout_from_idp(saml_idp_service)
|
||||
Runtime::Logger.debug("Logging out of IDP by visiting \"#{saml_idp_service.idp_sign_out_url}\"")
|
||||
|
||||
Support::Waiter.wait_until(sleep_interval: 1, reload_page: page) do
|
||||
page.visit saml_idp_service.idp_sign_out_url
|
||||
page.has_content?("You have been logged out.")
|
||||
end
|
||||
end
|
||||
|
||||
def enable_saml_sso(group, saml_idp_service)
|
||||
page.visit Runtime::Scenario.gitlab_address
|
||||
|
||||
Page::Main::Login.perform(&:sign_in_using_credentials) unless Page::Main::Menu.perform(&:signed_in?)
|
||||
|
||||
visit_saml_sso_settings(group)
|
||||
|
||||
Support::Retrier.retry_on_exception do
|
||||
EE::Page::Group::Settings::SamlSSO.perform do |saml_sso|
|
||||
saml_sso.set_id_provider_sso_url(saml_idp_service.idp_sso_url)
|
||||
saml_sso.set_cert_fingerprint(saml_idp_service.idp_certificate_fingerprint)
|
||||
saml_sso.click_save_changes
|
||||
|
||||
saml_sso.user_login_url_link_text
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def visit_saml_sso_settings(group, direct: false)
|
||||
if direct
|
||||
page.visit "#{group.web_url}/-/saml"
|
||||
else
|
||||
group.visit!
|
||||
|
||||
Page::Group::Menu.perform(&:go_to_saml_sso_group_settings)
|
||||
end
|
||||
# The toggle buttons take a moment to switch to the correct status.
|
||||
# I am not sure of a better, less complex way to wait for them to reflect their actual status.
|
||||
sleep 2
|
||||
end
|
||||
|
||||
def run_saml_idp_service(group_name)
|
||||
service = Service::DockerRun::SamlIdp.new(Runtime::Scenario.gitlab_address, group_name).tap do |runner|
|
||||
runner.pull
|
||||
runner.register!
|
||||
end
|
||||
|
||||
service
|
||||
end
|
||||
|
||||
def remove_saml_idp_service(saml_idp_service)
|
||||
saml_idp_service.remove!
|
||||
end
|
||||
|
||||
def login_to_idp_if_required(username, password)
|
||||
Vendor::SAMLIdp::Page::Login.perform { |login_page| login_page.login_if_required(username, password) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Flow
|
||||
module User
|
||||
module_function
|
||||
|
||||
def page
|
||||
Capybara.current_session
|
||||
end
|
||||
|
||||
def confirm_user(username)
|
||||
Flow::Login.while_signed_in_as_admin do
|
||||
Page::Main::Menu.perform(&:go_to_admin_area)
|
||||
Page::Admin::Menu.perform(&:go_to_users_overview)
|
||||
Page::Admin::Overview::Users::Index.perform do |index|
|
||||
index.search_user(username)
|
||||
index.click_user(username)
|
||||
end
|
||||
|
||||
Page::Admin::Overview::Users::Show.perform(&:confirm_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11,6 +11,10 @@ module QA
|
|||
post Runtime::API::Request.new(api_client, api_members_path).url, { user_id: user.id, access_level: access_level }
|
||||
end
|
||||
|
||||
def remove_member(user)
|
||||
delete Runtime::API::Request.new(api_client, "#{api_members_path}/#{user.id}").url
|
||||
end
|
||||
|
||||
def list_members
|
||||
JSON.parse(get(Runtime::API::Request.new(api_client, api_members_path).url).body)
|
||||
end
|
||||
|
|
|
@ -63,6 +63,10 @@ module QA
|
|||
'/groups'
|
||||
end
|
||||
|
||||
def api_delete_path
|
||||
"/groups/#{id}"
|
||||
end
|
||||
|
||||
def api_post_body
|
||||
{
|
||||
path: path,
|
||||
|
|
|
@ -38,6 +38,8 @@ module QA
|
|||
end
|
||||
|
||||
raise SetFeatureError, "#{key} was not enabled!" unless is_enabled
|
||||
|
||||
QA::Runtime::Logger.info("Successfully enabled and verified feature flag: #{key}")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Service
|
||||
module DockerRun
|
||||
class SamlIdp < Base
|
||||
def initialize(gitlab_host, group)
|
||||
@image = 'jamedjo/test-saml-idp'
|
||||
@name = 'saml-idp-server'
|
||||
@gitlab_host = gitlab_host
|
||||
@group = group
|
||||
super()
|
||||
end
|
||||
|
||||
def idp_base_url
|
||||
"https://#{host_name}:8443/simplesaml"
|
||||
end
|
||||
|
||||
def idp_sso_url
|
||||
"#{idp_base_url}/saml2/idp/SSOService.php"
|
||||
end
|
||||
|
||||
def idp_sign_out_url
|
||||
"#{idp_base_url}/module.php/core/authenticate.php?as=example-userpass&logout"
|
||||
end
|
||||
|
||||
def idp_signed_out_url
|
||||
"#{idp_base_url}/logout.php"
|
||||
end
|
||||
|
||||
def idp_metadata_url
|
||||
"#{idp_base_url}/saml2/idp/metadata.php"
|
||||
end
|
||||
|
||||
def idp_issuer
|
||||
idp_metadata_url
|
||||
end
|
||||
|
||||
def idp_certificate_fingerprint
|
||||
QA::Runtime::Env.simple_saml_fingerprint || '119b9e027959cdb7c662cfd075d9e2ef384e445f'
|
||||
end
|
||||
|
||||
def host_name
|
||||
return 'localhost' unless QA::Runtime::Env.running_in_ci?
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def register!
|
||||
command = <<~CMD.tr("\n", ' ')
|
||||
docker run -d --rm
|
||||
--network #{network}
|
||||
--hostname #{host_name}
|
||||
--name #{@name}
|
||||
--env SIMPLESAMLPHP_SP_ENTITY_ID=#{@gitlab_host}/groups/#{@group}
|
||||
--env SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE=#{@gitlab_host}/groups/#{@group}/-/saml/callback
|
||||
--publish 8080:8080
|
||||
--publish 8443:8443
|
||||
#{@image}
|
||||
CMD
|
||||
|
||||
command.gsub!("--network #{network} ", '') unless QA::Runtime::Env.running_in_ci?
|
||||
|
||||
shell command
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -281,7 +281,7 @@ describe Projects::CompareController do
|
|||
context 'when the user has access to the project' do
|
||||
render_views
|
||||
|
||||
let(:signature_commit) { build(:commit, project: project, safe_message: "message", sha: 'signature_commit') }
|
||||
let(:signature_commit) { project.commit_by(oid: '0b4bc9a49b562e85de7cc9e834518ea6828729b9') }
|
||||
let(:non_signature_commit) { build(:commit, project: project, safe_message: "message", sha: 'non_signature_commit') }
|
||||
|
||||
before do
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :x509_certificate do
|
||||
subject_key_identifier { 'BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC' }
|
||||
subject { 'CN=gitlab@example.org,OU=Example,O=World' }
|
||||
|
||||
email { 'gitlab@example.org' }
|
||||
serial_number { 278969561018901340486471282831158785578 }
|
||||
x509_issuer
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :x509_commit_signature do
|
||||
commit_sha { Digest::SHA1.hexdigest(SecureRandom.hex) }
|
||||
project
|
||||
x509_certificate
|
||||
verification_status { :verified }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :x509_issuer do
|
||||
subject_key_identifier { 'AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB' }
|
||||
subject { 'CN=PKI,OU=Example,O=World' }
|
||||
|
||||
crl_url { 'http://example.com/pki.crl' }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,35 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Blob Header Filepath rendering matches the snapshot 1`] = `
|
||||
<div
|
||||
class="file-header-content d-flex align-items-center lh-100"
|
||||
>
|
||||
|
||||
<file-icon-stub
|
||||
aria-hidden="true"
|
||||
cssclasses="mr-2"
|
||||
filename="dummy.md"
|
||||
size="18"
|
||||
/>
|
||||
|
||||
<strong
|
||||
class="file-title-name qa-file-title-name mr-1 js-blob-header-filepath"
|
||||
>
|
||||
dummy.md
|
||||
</strong>
|
||||
|
||||
<small
|
||||
class="mr-2"
|
||||
>
|
||||
a lot
|
||||
</small>
|
||||
|
||||
<clipboard-button-stub
|
||||
cssclass="btn-clipboard btn-transparent lh-100 position-static"
|
||||
gfm="\`dummy.md\`"
|
||||
text="dummy.md"
|
||||
title="Copy file path"
|
||||
tooltipplacement="top"
|
||||
/>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,90 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import BlobHeaderFilepath from '~/blob/components/blob_header_filepath.vue';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import { Blob as MockBlob } from './mock_data';
|
||||
import { numberToHumanSize } from '~/lib/utils/number_utils';
|
||||
|
||||
const mockHumanReadableSize = 'a lot';
|
||||
jest.mock('~/lib/utils/number_utils', () => ({
|
||||
numberToHumanSize: jest.fn(() => mockHumanReadableSize),
|
||||
}));
|
||||
|
||||
describe('Blob Header Filepath', () => {
|
||||
let wrapper;
|
||||
|
||||
function createComponent(blobProps = {}, options = {}) {
|
||||
wrapper = shallowMount(BlobHeaderFilepath, {
|
||||
propsData: {
|
||||
blob: Object.assign({}, MockBlob, blobProps),
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
it('matches the snapshot', () => {
|
||||
createComponent();
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders regular name', () => {
|
||||
createComponent();
|
||||
expect(
|
||||
wrapper
|
||||
.find('.js-blob-header-filepath')
|
||||
.text()
|
||||
.trim(),
|
||||
).toBe(MockBlob.name);
|
||||
});
|
||||
|
||||
it('does not fail if the name is empty', () => {
|
||||
const emptyName = '';
|
||||
createComponent({ name: emptyName });
|
||||
expect(wrapper.find('.js-blob-header-filepath').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders copy-to-clipboard icon that copies path of the Blob', () => {
|
||||
createComponent();
|
||||
const btn = wrapper.find(ClipboardButton);
|
||||
expect(btn.exists()).toBe(true);
|
||||
expect(btn.vm.text).toBe(MockBlob.path);
|
||||
});
|
||||
|
||||
it('renders filesize in a human-friendly format', () => {
|
||||
createComponent();
|
||||
expect(numberToHumanSize).toHaveBeenCalled();
|
||||
expect(wrapper.vm.blobSize).toBe(mockHumanReadableSize);
|
||||
});
|
||||
|
||||
it('renders a slot and prepends its contents to the existing one', () => {
|
||||
const slotContent = 'Foo Bar';
|
||||
createComponent(
|
||||
{},
|
||||
{
|
||||
scopedSlots: {
|
||||
filepathPrepend: `<span>${slotContent}</span>`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(wrapper.text()).toContain(slotContent);
|
||||
expect(
|
||||
wrapper
|
||||
.text()
|
||||
.trim()
|
||||
.substring(0, slotContent.length),
|
||||
).toBe(slotContent);
|
||||
});
|
||||
});
|
||||
|
||||
describe('functionality', () => {
|
||||
it('sets gfm value correctly on the clipboard-button', () => {
|
||||
createComponent();
|
||||
expect(wrapper.vm.gfmCopyText).toBe('`dummy.md`');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
export const Blob = {
|
||||
binary: false,
|
||||
highlightedData:
|
||||
'<h1 data-sourcepos="1:1-1:19" dir="auto">\n<a id="user-content-this-one-is-dummy" class="anchor" href="#this-one-is-dummy" aria-hidden="true"></a>This one is dummy</h1>\n<h2 data-sourcepos="3:1-3:21" dir="auto">\n<a id="user-content-and-has-sub-header" class="anchor" href="#and-has-sub-header" aria-hidden="true"></a>And has sub-header</h2>\n<p data-sourcepos="5:1-5:27" dir="auto">Even some stupid text here</p>',
|
||||
name: 'dummy.md',
|
||||
path: 'dummy.md',
|
||||
rawPath: '/flightjs/flight/snippets/51/raw',
|
||||
size: 75,
|
||||
simpleViewer: {
|
||||
collapsed: false,
|
||||
fileType: 'text',
|
||||
loadAsync: true,
|
||||
loadingPartialName: 'loading',
|
||||
renderError: null,
|
||||
tooLarge: false,
|
||||
type: 'simple',
|
||||
},
|
||||
richViewer: {
|
||||
collapsed: false,
|
||||
fileType: 'markup',
|
||||
loadAsync: true,
|
||||
loadingPartialName: 'loading',
|
||||
renderError: null,
|
||||
tooLarge: false,
|
||||
type: 'rich',
|
||||
},
|
||||
};
|
||||
|
||||
export default {};
|
|
@ -0,0 +1,208 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::X509::Commit do
|
||||
describe '#signature' do
|
||||
let(:signature) { described_class.new(commit).signature }
|
||||
|
||||
let(:user1_certificate_attributes) do
|
||||
{
|
||||
subject_key_identifier: X509Helpers::User1.certificate_subject_key_identifier,
|
||||
subject: X509Helpers::User1.certificate_subject,
|
||||
email: X509Helpers::User1.certificate_email,
|
||||
serial_number: X509Helpers::User1.certificate_serial
|
||||
}
|
||||
end
|
||||
|
||||
let(:user1_issuer_attributes) do
|
||||
{
|
||||
subject_key_identifier: X509Helpers::User1.issuer_subject_key_identifier,
|
||||
subject: X509Helpers::User1.certificate_issuer,
|
||||
crl_url: X509Helpers::User1.certificate_crl
|
||||
}
|
||||
end
|
||||
|
||||
shared_examples 'returns the cached signature on second call' do
|
||||
it 'returns the cached signature on second call' do
|
||||
x509_commit = described_class.new(commit)
|
||||
|
||||
expect(x509_commit).to receive(:create_cached_signature).and_call_original
|
||||
signature
|
||||
|
||||
# consecutive call
|
||||
expect(x509_commit).not_to receive(:create_cached_signature).and_call_original
|
||||
signature
|
||||
end
|
||||
end
|
||||
|
||||
let!(:project) { create :project, :repository, path: X509Helpers::User1.path }
|
||||
let!(:commit_sha) { X509Helpers::User1.commit }
|
||||
|
||||
context 'unsigned commit' do
|
||||
let!(:commit) { create :commit, project: project, sha: commit_sha }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(described_class.new(commit).signature).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'valid signature from known user' do
|
||||
let!(:commit) { create :commit, project: project, sha: commit_sha, created_at: Time.utc(2019, 1, 1, 20, 15, 0), committer_email: X509Helpers::User1.emails.first }
|
||||
|
||||
let!(:user) { create(:user, email: X509Helpers::User1.emails.first) }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
|
||||
.with(Gitlab::Git::Repository, commit_sha)
|
||||
.and_return(
|
||||
[
|
||||
X509Helpers::User1.signed_commit_signature,
|
||||
X509Helpers::User1.signed_commit_base_data
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns an unverified signature' do
|
||||
expect(signature).to have_attributes(
|
||||
commit_sha: commit_sha,
|
||||
project: project,
|
||||
verification_status: 'unverified'
|
||||
)
|
||||
expect(signature.x509_certificate).to have_attributes(user1_certificate_attributes)
|
||||
expect(signature.x509_certificate.x509_issuer).to have_attributes(user1_issuer_attributes)
|
||||
expect(signature.persisted?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'verified signature from known user' do
|
||||
let!(:commit) { create :commit, project: project, sha: commit_sha, created_at: Time.utc(2019, 1, 1, 20, 15, 0), committer_email: X509Helpers::User1.emails.first }
|
||||
|
||||
let!(:user) { create(:user, email: X509Helpers::User1.emails.first) }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
|
||||
.with(Gitlab::Git::Repository, commit_sha)
|
||||
.and_return(
|
||||
[
|
||||
X509Helpers::User1.signed_commit_signature,
|
||||
X509Helpers::User1.signed_commit_base_data
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
context 'with trusted certificate store' do
|
||||
before do
|
||||
store = OpenSSL::X509::Store.new
|
||||
certificate = OpenSSL::X509::Certificate.new X509Helpers::User1.trust_cert
|
||||
store.add_cert(certificate)
|
||||
allow(OpenSSL::X509::Store).to receive(:new)
|
||||
.and_return(
|
||||
store
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns a verified signature' do
|
||||
expect(signature).to have_attributes(
|
||||
commit_sha: commit_sha,
|
||||
project: project,
|
||||
verification_status: 'verified'
|
||||
)
|
||||
expect(signature.x509_certificate).to have_attributes(user1_certificate_attributes)
|
||||
expect(signature.x509_certificate.x509_issuer).to have_attributes(user1_issuer_attributes)
|
||||
expect(signature.persisted?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'without trusted certificate within store' do
|
||||
before do
|
||||
store = OpenSSL::X509::Store.new
|
||||
allow(OpenSSL::X509::Store).to receive(:new)
|
||||
.and_return(
|
||||
store
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns an unverified signature' do
|
||||
expect(signature).to have_attributes(
|
||||
commit_sha: commit_sha,
|
||||
project: project,
|
||||
verification_status: 'unverified'
|
||||
)
|
||||
expect(signature.x509_certificate).to have_attributes(user1_certificate_attributes)
|
||||
expect(signature.x509_certificate.x509_issuer).to have_attributes(user1_issuer_attributes)
|
||||
expect(signature.persisted?).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'unverified signature from unknown user' do
|
||||
let!(:commit) { create :commit, project: project, sha: commit_sha, created_at: Time.utc(2019, 1, 1, 20, 15, 0), committer_email: X509Helpers::User1.emails.first }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
|
||||
.with(Gitlab::Git::Repository, commit_sha)
|
||||
.and_return(
|
||||
[
|
||||
X509Helpers::User1.signed_commit_signature,
|
||||
X509Helpers::User1.signed_commit_base_data
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns an unverified signature' do
|
||||
expect(signature).to have_attributes(
|
||||
commit_sha: commit_sha,
|
||||
project: project,
|
||||
verification_status: 'unverified'
|
||||
)
|
||||
expect(signature.x509_certificate).to have_attributes(user1_certificate_attributes)
|
||||
expect(signature.x509_certificate.x509_issuer).to have_attributes(user1_issuer_attributes)
|
||||
expect(signature.persisted?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid signature' do
|
||||
let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: X509Helpers::User1.emails.first }
|
||||
|
||||
let!(:user) { create(:user, email: X509Helpers::User1.emails.first) }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
|
||||
.with(Gitlab::Git::Repository, commit_sha)
|
||||
.and_return(
|
||||
[
|
||||
# Corrupt the key
|
||||
X509Helpers::User1.signed_commit_signature.tr('A', 'B'),
|
||||
X509Helpers::User1.signed_commit_base_data
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns nil' do
|
||||
expect(described_class.new(commit).signature).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid commit message' do
|
||||
let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: X509Helpers::User1.emails.first }
|
||||
|
||||
let!(:user) { create(:user, email: X509Helpers::User1.emails.first) }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
|
||||
.with(Gitlab::Git::Repository, commit_sha)
|
||||
.and_return(
|
||||
[
|
||||
X509Helpers::User1.signed_commit_signature,
|
||||
# Corrupt the commit message
|
||||
'x'
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns nil' do
|
||||
expect(described_class.new(commit).signature).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require Rails.root.join('db', 'post_migrate', '20200206091544_migrate_create_commit_signature_worker_sidekiq_queue.rb')
|
||||
|
||||
describe MigrateCreateCommitSignatureWorkerSidekiqQueue, :sidekiq, :redis do
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
include StubWorker
|
||||
|
||||
context 'when there are jobs in the queue' do
|
||||
it 'correctly migrates queue when migrating up' do
|
||||
Sidekiq::Testing.disable! do
|
||||
stub_worker(queue: 'create_commit_signature').perform_async('Something', [1])
|
||||
stub_worker(queue: 'create_gpg_signature').perform_async('Something', [1])
|
||||
|
||||
described_class.new.up
|
||||
|
||||
expect(sidekiq_queue_length('create_gpg_signature')).to eq 0
|
||||
expect(sidekiq_queue_length('create_commit_signature')).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
it 'correctly migrates queue when migrating down' do
|
||||
Sidekiq::Testing.disable! do
|
||||
stub_worker(queue: 'create_gpg_signature').perform_async('Something', [1])
|
||||
|
||||
described_class.new.down
|
||||
|
||||
expect(sidekiq_queue_length('create_gpg_signature')).to eq 1
|
||||
expect(sidekiq_queue_length('create_commit_signature')).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are no jobs in the queues' do
|
||||
it 'does not raise error when migrating up' do
|
||||
expect { described_class.new.up }.not_to raise_error
|
||||
end
|
||||
|
||||
it 'does not raise error when migrating down' do
|
||||
expect { described_class.new.down }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
|
@ -671,4 +671,25 @@ eos
|
|||
expect(commit2.merge_requests).to contain_exactly(merge_request1)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'signed commits' do
|
||||
let(:gpg_signed_commit) { project.commit_by(oid: '0b4bc9a49b562e85de7cc9e834518ea6828729b9') }
|
||||
let(:x509_signed_commit) { project.commit_by(oid: '189a6c924013fc3fe40d6f1ec1dc20214183bc97') }
|
||||
let(:unsigned_commit) { project.commit_by(oid: '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51') }
|
||||
let!(:commit) { create(:commit, project: project) }
|
||||
|
||||
it 'returns signature_type properly' do
|
||||
expect(gpg_signed_commit.signature_type).to eq(:PGP)
|
||||
expect(x509_signed_commit.signature_type).to eq(:X509)
|
||||
expect(unsigned_commit.signature_type).to eq(:NONE)
|
||||
expect(commit.signature_type).to eq(:NONE)
|
||||
end
|
||||
|
||||
it 'returns has_signature? properly' do
|
||||
expect(gpg_signed_commit.has_signature?).to be_truthy
|
||||
expect(x509_signed_commit.has_signature?).to be_truthy
|
||||
expect(unsigned_commit.has_signature?).to be_falsey
|
||||
expect(commit.has_signature?).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe X509SerialNumberAttribute do
|
||||
let(:model) { Class.new { include X509SerialNumberAttribute } }
|
||||
|
||||
before do
|
||||
columns = [
|
||||
double(:column, name: 'name', type: :text),
|
||||
double(:column, name: 'serial_number', type: :binary)
|
||||
]
|
||||
|
||||
allow(model).to receive(:columns).and_return(columns)
|
||||
end
|
||||
|
||||
describe '#x509_serial_number_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 x509 serial number attribute for a binary column' do
|
||||
expect(model).to receive(:attribute)
|
||||
.with(:serial_number, an_instance_of(Gitlab::Database::X509SerialNumberAttribute))
|
||||
|
||||
model.x509_serial_number_attribute(:serial_number)
|
||||
end
|
||||
|
||||
it 'raises ArgumentError when the column type is not :binary' do
|
||||
expect { model.x509_serial_number_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.x509_serial_number_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.x509_serial_number_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.x509_serial_number_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 x509 serial number attribute' do
|
||||
expect(model).not_to receive(:table_exists?)
|
||||
expect(model).not_to receive(:columns)
|
||||
expect(model).to receive(:attribute).with(:serial_number, an_instance_of(Gitlab::Database::X509SerialNumberAttribute))
|
||||
|
||||
model.x509_serial_number_attribute(:serial_number)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,107 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe X509Certificate do
|
||||
describe 'validation' do
|
||||
it { is_expected.to validate_presence_of(:subject_key_identifier) }
|
||||
it { is_expected.to validate_presence_of(:subject) }
|
||||
it { is_expected.to validate_presence_of(:email) }
|
||||
it { is_expected.to validate_presence_of(:serial_number) }
|
||||
it { is_expected.to validate_presence_of(:x509_issuer_id) }
|
||||
end
|
||||
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:x509_issuer).required }
|
||||
end
|
||||
|
||||
describe '.safe_create!' do
|
||||
let(:subject_key_identifier) { 'CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD' }
|
||||
let(:subject) { 'CN=gitlab@example.com,OU=Example,O=World' }
|
||||
let(:email) { 'gitlab@example.com' }
|
||||
let(:serial_number) { '123456789' }
|
||||
let(:issuer) { create(:x509_issuer) }
|
||||
|
||||
let(:attributes) do
|
||||
{
|
||||
subject_key_identifier: subject_key_identifier,
|
||||
subject: subject,
|
||||
email: email,
|
||||
serial_number: serial_number,
|
||||
x509_issuer_id: issuer.id
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates a new certificate if it was not found' do
|
||||
expect { described_class.safe_create!(attributes) }.to change { described_class.count }.by(1)
|
||||
end
|
||||
|
||||
it 'assigns the correct attributes when creating' do
|
||||
certificate = described_class.safe_create!(attributes)
|
||||
|
||||
expect(certificate.subject_key_identifier).to eq(subject_key_identifier)
|
||||
expect(certificate.subject).to eq(subject)
|
||||
expect(certificate.email).to eq(email)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'validators' do
|
||||
it 'accepts correct subject_key_identifier' do
|
||||
subject_key_identifiers = [
|
||||
'AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB',
|
||||
'CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD'
|
||||
]
|
||||
|
||||
subject_key_identifiers.each do |identifier|
|
||||
expect(build(:x509_certificate, subject_key_identifier: identifier)).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
it 'rejects invalid subject_key_identifier' do
|
||||
subject_key_identifiers = [
|
||||
'AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB',
|
||||
'CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:GG',
|
||||
'random string',
|
||||
'12321342545356434523412341245452345623453542345234523453245'
|
||||
]
|
||||
|
||||
subject_key_identifiers.each do |identifier|
|
||||
expect(build(:x509_certificate, subject_key_identifier: identifier)).to be_invalid
|
||||
end
|
||||
end
|
||||
|
||||
it 'accepts correct email address' do
|
||||
emails = [
|
||||
'smime@example.org',
|
||||
'smime@example.com'
|
||||
]
|
||||
|
||||
emails.each do |email|
|
||||
expect(build(:x509_certificate, email: email)).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
it 'rejects invalid email' do
|
||||
emails = [
|
||||
'this is not an email',
|
||||
'@example.org'
|
||||
]
|
||||
|
||||
emails.each do |email|
|
||||
expect(build(:x509_certificate, email: email)).to be_invalid
|
||||
end
|
||||
end
|
||||
|
||||
it 'accepts valid serial_number' do
|
||||
expect(build(:x509_certificate, serial_number: 123412341234)).to be_valid
|
||||
|
||||
# rfc 5280 - 4.1.2.2 Serial number (20 octets is the maximum)
|
||||
expect(build(:x509_certificate, serial_number: 1461501637330902918203684832716283019655932542975)).to be_valid
|
||||
expect(build(:x509_certificate, serial_number: 'ffffffffffffffffffffffffffffffffffffffff'.to_i(16))).to be_valid
|
||||
end
|
||||
|
||||
it 'rejects invalid serial_number' do
|
||||
expect(build(:x509_certificate, serial_number: "sgsgfsdgdsfg")).to be_invalid
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe X509CommitSignature do
|
||||
let(:commit_sha) { '189a6c924013fc3fe40d6f1ec1dc20214183bc97' }
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let!(:commit) { create(:commit, project: project, sha: commit_sha) }
|
||||
let(:x509_certificate) { create(:x509_certificate) }
|
||||
let(:x509_signature) { create(:x509_commit_signature, commit_sha: commit_sha) }
|
||||
|
||||
it_behaves_like 'having unique enum values'
|
||||
|
||||
describe 'validation' do
|
||||
it { is_expected.to validate_presence_of(:commit_sha) }
|
||||
it { is_expected.to validate_presence_of(:project_id) }
|
||||
it { is_expected.to validate_presence_of(:x509_certificate_id) }
|
||||
end
|
||||
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:project).required }
|
||||
it { is_expected.to belong_to(:x509_certificate).required }
|
||||
end
|
||||
|
||||
describe '.safe_create!' do
|
||||
let(:attributes) do
|
||||
{
|
||||
commit_sha: commit_sha,
|
||||
project: project,
|
||||
x509_certificate_id: x509_certificate.id,
|
||||
verification_status: "verified"
|
||||
}
|
||||
end
|
||||
|
||||
it 'finds a signature by commit sha if it existed' do
|
||||
x509_signature
|
||||
|
||||
expect(described_class.safe_create!(commit_sha: commit_sha)).to eq(x509_signature)
|
||||
end
|
||||
|
||||
it 'creates a new signature if it was not found' do
|
||||
expect { described_class.safe_create!(attributes) }.to change { described_class.count }.by(1)
|
||||
end
|
||||
|
||||
it 'assigns the correct attributes when creating' do
|
||||
signature = described_class.safe_create!(attributes)
|
||||
|
||||
expect(signature.project).to eq(project)
|
||||
expect(signature.commit_sha).to eq(commit_sha)
|
||||
expect(signature.x509_certificate_id).to eq(x509_certificate.id)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,71 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe X509Issuer do
|
||||
describe 'validation' do
|
||||
it { is_expected.to validate_presence_of(:subject_key_identifier) }
|
||||
it { is_expected.to validate_presence_of(:subject) }
|
||||
it { is_expected.to validate_presence_of(:crl_url) }
|
||||
end
|
||||
|
||||
describe '.safe_create!' do
|
||||
let(:issuer_subject_key_identifier) { 'AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB' }
|
||||
let(:issuer_subject) { 'CN=PKI,OU=Example,O=World' }
|
||||
let(:issuer_crl_url) { 'http://example.com/pki.crl' }
|
||||
|
||||
let(:attributes) do
|
||||
{
|
||||
subject_key_identifier: issuer_subject_key_identifier,
|
||||
subject: issuer_subject,
|
||||
crl_url: issuer_crl_url
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates a new issuer if it was not found' do
|
||||
expect { described_class.safe_create!(attributes) }.to change { described_class.count }.by(1)
|
||||
end
|
||||
|
||||
it 'assigns the correct attributes when creating' do
|
||||
issuer = described_class.safe_create!(attributes)
|
||||
|
||||
expect(issuer.subject_key_identifier).to eq(issuer_subject_key_identifier)
|
||||
expect(issuer.subject).to eq(issuer_subject)
|
||||
expect(issuer.crl_url).to eq(issuer_crl_url)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'validators' do
|
||||
it 'accepts correct subject_key_identifier' do
|
||||
subject_key_identifiers = [
|
||||
'AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB',
|
||||
'CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD'
|
||||
]
|
||||
|
||||
subject_key_identifiers.each do |identifier|
|
||||
expect(build(:x509_issuer, subject_key_identifier: identifier)).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
it 'rejects invalid subject_key_identifier' do
|
||||
subject_key_identifiers = [
|
||||
'AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB',
|
||||
'CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:GG',
|
||||
'random string',
|
||||
'12321342545356434523412341245452345623453542345234523453245'
|
||||
]
|
||||
|
||||
subject_key_identifiers.each do |identifier|
|
||||
expect(build(:x509_issuer, subject_key_identifier: identifier)).to be_invalid
|
||||
end
|
||||
end
|
||||
|
||||
it 'accepts valid crl_url' do
|
||||
expect(build(:x509_issuer, crl_url: "https://pki.example.org")).to be_valid
|
||||
end
|
||||
|
||||
it 'rejects invalid crl_url' do
|
||||
expect(build(:x509_issuer, crl_url: "ht://pki.example.org")).to be_invalid
|
||||
end
|
||||
end
|
||||
end
|
|
@ -461,7 +461,7 @@ describe API::Users do
|
|||
end
|
||||
|
||||
it "creates user with optional attributes" do
|
||||
optional_attributes = { confirm: true }
|
||||
optional_attributes = { confirm: true, theme_id: 2, color_scheme_id: 4 }
|
||||
attributes = attributes_for(:user).merge(optional_attributes)
|
||||
|
||||
post api('/users', admin), params: attributes
|
||||
|
@ -576,6 +576,15 @@ describe API::Users do
|
|||
expect(response).to have_gitlab_http_status(400)
|
||||
end
|
||||
|
||||
it "doesn't create user with invalid optional attributes" do
|
||||
optional_attributes = { theme_id: 50, color_scheme_id: 50 }
|
||||
attributes = attributes_for(:user).merge(optional_attributes)
|
||||
|
||||
post api('/users', admin), params: attributes
|
||||
|
||||
expect(response).to have_gitlab_http_status(400)
|
||||
end
|
||||
|
||||
it 'returns 400 error if user does not validate' do
|
||||
post api('/users', admin),
|
||||
params: {
|
||||
|
@ -824,6 +833,34 @@ describe API::Users do
|
|||
expect(user.reload.email).not_to eq('invalid email')
|
||||
end
|
||||
|
||||
it "updates theme id" do
|
||||
put api("/users/#{user.id}", admin), params: { theme_id: 5 }
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(user.reload.theme_id).to eq(5)
|
||||
end
|
||||
|
||||
it "does not update invalid theme id" do
|
||||
put api("/users/#{user.id}", admin), params: { theme_id: 50 }
|
||||
|
||||
expect(response).to have_gitlab_http_status(400)
|
||||
expect(user.reload.theme_id).not_to eq(50)
|
||||
end
|
||||
|
||||
it "updates color scheme id" do
|
||||
put api("/users/#{user.id}", admin), params: { color_scheme_id: 5 }
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(user.reload.color_scheme_id).to eq(5)
|
||||
end
|
||||
|
||||
it "does not update invalid color scheme id" do
|
||||
put api("/users/#{user.id}", admin), params: { color_scheme_id: 50 }
|
||||
|
||||
expect(response).to have_gitlab_http_status(400)
|
||||
expect(user.reload.color_scheme_id).not_to eq(50)
|
||||
end
|
||||
|
||||
context 'when the current user is not an admin' do
|
||||
it "is not available" do
|
||||
expect do
|
||||
|
|
|
@ -214,23 +214,23 @@ describe Git::BranchHooksService do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'GPG signatures' do
|
||||
describe 'signatures' do
|
||||
context 'when the commit has a signature' do
|
||||
context 'when the signature is already cached' do
|
||||
before do
|
||||
create(:gpg_signature, commit_sha: commit.id)
|
||||
end
|
||||
|
||||
it 'does not queue a CreateGpgSignatureWorker' do
|
||||
expect(CreateGpgSignatureWorker).not_to receive(:perform_async)
|
||||
it 'does not queue a CreateCommitSignatureWorker' do
|
||||
expect(CreateCommitSignatureWorker).not_to receive(:perform_async)
|
||||
|
||||
service.execute
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the signature is not yet cached' do
|
||||
it 'queues a CreateGpgSignatureWorker' do
|
||||
expect(CreateGpgSignatureWorker).to receive(:perform_async).with([commit.id], project.id)
|
||||
it 'queues a CreateCommitSignatureWorker' do
|
||||
expect(CreateCommitSignatureWorker).to receive(:perform_async).with([commit.id], project.id)
|
||||
|
||||
service.execute
|
||||
end
|
||||
|
@ -240,7 +240,7 @@ describe Git::BranchHooksService do
|
|||
.to receive(:shas_with_signatures)
|
||||
.and_return([sample_commit.id, another_sample_commit.id])
|
||||
|
||||
expect(CreateGpgSignatureWorker)
|
||||
expect(CreateCommitSignatureWorker)
|
||||
.to receive(:perform_async)
|
||||
.with([sample_commit.id, another_sample_commit.id], project.id)
|
||||
|
||||
|
@ -257,8 +257,8 @@ describe Git::BranchHooksService do
|
|||
.and_return([])
|
||||
end
|
||||
|
||||
it 'does not queue a CreateGpgSignatureWorker' do
|
||||
expect(CreateGpgSignatureWorker)
|
||||
it 'does not queue a CreateCommitSignatureWorker' do
|
||||
expect(CreateCommitSignatureWorker)
|
||||
.not_to receive(:perform_async)
|
||||
.with(sample_commit.id, project.id)
|
||||
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module X509Helpers
|
||||
module User1
|
||||
extend self
|
||||
|
||||
def commit
|
||||
'a4df3c87f040f5fa693d4d55a89b6af74e22cb56'
|
||||
end
|
||||
|
||||
def path
|
||||
'gitlab-test'
|
||||
end
|
||||
|
||||
def trust_cert
|
||||
<<~TRUSTCERTIFICATE
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIGVTCCBD2gAwIBAgIEdikH4zANBgkqhkiG9w0BAQsFADCBmTELMAkGA1UEBhMC
|
||||
REUxDzANBgNVBAgMBkJheWVybjERMA8GA1UEBwwITXVlbmNoZW4xEDAOBgNVBAoM
|
||||
B1NpZW1lbnMxETAPBgNVBAUTCFpaWlpaWkExMR0wGwYDVQQLDBRTaWVtZW5zIFRy
|
||||
dXN0IENlbnRlcjEiMCAGA1UEAwwZU2llbWVucyBSb290IENBIFYzLjAgMjAxNjAe
|
||||
Fw0xNjA2MDYxMzMwNDhaFw0yODA2MDYxMzMwNDhaMIGZMQswCQYDVQQGEwJERTEP
|
||||
MA0GA1UECAwGQmF5ZXJuMREwDwYDVQQHDAhNdWVuY2hlbjEQMA4GA1UECgwHU2ll
|
||||
bWVuczERMA8GA1UEBRMIWlpaWlpaQTExHTAbBgNVBAsMFFNpZW1lbnMgVHJ1c3Qg
|
||||
Q2VudGVyMSIwIAYDVQQDDBlTaWVtZW5zIFJvb3QgQ0EgVjMuMCAyMDE2MIICIjAN
|
||||
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp2k2PcfRBu1yeXUxG3UoEDDTFtgF
|
||||
zGVNIq4j4g6niE7hxZzoferzgC6bK3y+lOQFfNkctFzjq6N+JvH535KnN4vXvNoO
|
||||
/Rvrn38XtUC8ms2/1MlzvFDMh0Rt1HzemJYsSUXPvj5EMjGVzeQu1/GZhN6XlRrc
|
||||
SgMSeuwAGN4IX/0QIyxaArxlDZks6zSOA+s9t2PBp6vPZcqA9y4RZLc33nQmdwZg
|
||||
onEYK55xS1QFY2/zuZGQtB73e69IsrAxP+ZzrivlpbgKkEb1kt0qd7rLkp/HnM9J
|
||||
IDFc6uo8dAUCA/oR40Yfe2+8hyKoTrFbTvxC2SqxoBolAemZ2rnckuQ1RInbCQNp
|
||||
pBJJr/Hg78yvIp65gP6mZsyhL6ZLLXjL+ICIUTU86OedkJ7j9o4vdrwBn8AugENy
|
||||
8jAMu06k9CFbe7QoEynlRvm5VoYMSBsMqn7lAmuBcuMHdEdXu/qN/ULRLGkx1QRc
|
||||
gqf7+QszYla8QEaTtxQKWfdAU0Fyg0ROagrBtFjuDjsMeLK6LM17K3FFM3pghISj
|
||||
o4A8+y2fSbKKnMvU1z3Zey6vnGSwZKOxMJy5/aWuERbegQ07iH0jaA7S/gKZhOKO
|
||||
uDHD9qOBYfKou6wC+xdWyPGFPOq8BQRkWrSEeQW9FxhyYhhcCdcRh+hpZ4eHgRLM
|
||||
KkiFrljndwyB4eUCAwEAAaOBojCBnzAfBgNVHSMEGDAWgBRwbaBQ7KnQLGedGRX+
|
||||
/QRzNcPi1DAPBgNVHRMBAf8EBTADAQH/MDwGA1UdIAQ1MDMwMQYEVR0gADApMCcG
|
||||
CCsGAQUFBwIBFhtodHRwOi8vd3d3LnNpZW1lbnMuY29tL3BraS8wDgYDVR0PAQH/
|
||||
BAQDAgEGMB0GA1UdDgQWBBRwbaBQ7KnQLGedGRX+/QRzNcPi1DANBgkqhkiG9w0B
|
||||
AQsFAAOCAgEAHAxI694Yl16uKvWUdGDoglYLXmTxkVHOSci3TxzdEsAJ6WEf7kbj
|
||||
6zSQxGcAOz7nvto80rOZzlCluoO5K5fD7a4nEKl+tuBPrgtcEE8nkspPJF6DwjHQ
|
||||
Lmh219YxktZ1D7egLaRCGvxbPjkb3Wuh4vLqzZHr8twcauMxMyqRTN5F2+F43MY0
|
||||
AeBIb9QIMYsxxLBxsSeg4aajGwhdj5FmDFUFbGlyIjd0FfnXxvMuRtWpUWOu4Tya
|
||||
kA0AX/q6uM/L9SFIwmzTO7+2AHW/m/HrCmWb6R4VYWAgppp+jhUViW5l1uLB3i4m
|
||||
5IaJHZilU/DwQ5FnkuP2xqLvZ7AF3uXBlldOAbE1327uGIhYgp40Oi7PIHH+vgwg
|
||||
JOXQJ3SMwEzYmxCNsyLKAJb2Gs1IpwEpz7lpitl7i/DeUlPZSAo+1SLzc7P35muX
|
||||
ukCeh1vR7LJdCeYQpDpKeUYjKaNXr2/rZlMFmOGXLBKQvTNcI2I5WTIbVQ1sxhWN
|
||||
0FS+INH6jUypiwh0WH2R1Bo0HY3Lq4zJJ3Ct/12ocQ78+JfENXI8glOs3H07jyng
|
||||
afEj0ba23cn4HnV8s4T0jt8KZYlNkSNlSJ5kgTaZjmdLbTbt24OO4f3WNRrINwKC
|
||||
VzrN1ydSBGHNOsb/muR5axK/dHN2TEycRJPO6kSaVclLhMTxEmhRBUE=
|
||||
-----END CERTIFICATE-----
|
||||
TRUSTCERTIFICATE
|
||||
end
|
||||
|
||||
def signed_commit_signature
|
||||
<<~SIGNATURE
|
||||
-----BEGIN SIGNED MESSAGE-----
|
||||
MIISUgYJKoZIhvcNAQcCoIISQzCCEj8CAQExDTALBglghkgBZQMEAgEwCwYJKoZI
|
||||
hvcNAQcBoIIP3TCCB2kwggVRoAMCAQICBGvn1/4wDQYJKoZIhvcNAQELBQAwgZ8x
|
||||
CzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCYXllcm4xETAPBgNVBAcMCE11ZW5jaGVu
|
||||
MRAwDgYDVQQKDAdTaWVtZW5zMREwDwYDVQQFEwhaWlpaWlpBMjEdMBsGA1UECwwU
|
||||
U2llbWVucyBUcnVzdCBDZW50ZXIxKDAmBgNVBAMMH1NpZW1lbnMgSXNzdWluZyBD
|
||||
QSBFRSBBdXRoIDIwMTYwHhcNMTcwMjAzMDY1MzUyWhcNMjAwMjAzMDY1MzUyWjBb
|
||||
MREwDwYDVQQFEwhaMDAwTldESDEOMAwGA1UEKgwFUm9nZXIxDjAMBgNVBAQMBU1l
|
||||
aWVyMRAwDgYDVQQKDAdTaWVtZW5zMRQwEgYDVQQDDAtNZWllciBSb2dlcjCCASIw
|
||||
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIpqbpRAtn+vetgVb+APuoVOytZx
|
||||
TmfWovp22nsmJQwE8ZgrJihRjIez0wjD3cvSREvWUXsvbiyxrSHmmwycRCV9YGi1
|
||||
Y9vaYRKOrWhT64Xv6wq6oq8VoA5J3z6V5P6Tkj7g9Q3OskRuSbhFQY89VUdsea+N
|
||||
mcv/XrwtQR0SekfSZw9k0LhbauE69SWRV26O03raengjecbbkS+GTlP30/CqPzzQ
|
||||
4Ac2TmmVF7RlkGRB05mJqHS+nDK7Lmcr7jD0e92YW+v8Lft4Qu3MpFTYVa7zk712
|
||||
5xWAgedyOaJb6TpJEz8KRX8v3i0PilQnuKAqZFkLjNcydOox0AtYRW1P2iMCAwEA
|
||||
AaOCAu4wggLqMB0GA1UdDgQWBBTsALUoAlzTpaGrwqE0gYSqv5vP+DBDBgNVHREE
|
||||
PDA6oCMGCisGAQQBgjcUAgOgFQwTci5tZWllckBzaWVtZW5zLmNvbYETci5tZWll
|
||||
ckBzaWVtZW5zLmNvbTAOBgNVHQ8BAf8EBAMCB4AwKQYDVR0lBCIwIAYIKwYBBQUH
|
||||
AwIGCCsGAQUFBwMEBgorBgEEAYI3FAICMIHKBgNVHR8EgcIwgb8wgbyggbmggbaG
|
||||
Jmh0dHA6Ly9jaC5zaWVtZW5zLmNvbS9wa2k/WlpaWlpaQTIuY3JshkFsZGFwOi8v
|
||||
Y2wuc2llbWVucy5uZXQvQ049WlpaWlpaQTIsTD1QS0k/Y2VydGlmaWNhdGVSZXZv
|
||||
Y2F0aW9uTGlzdIZJbGRhcDovL2NsLnNpZW1lbnMuY29tL0NOPVpaWlpaWkEyLG89
|
||||
VHJ1c3RjZW50ZXI/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDBFBgNVHSAEPjA8
|
||||
MDoGDSsGAQQBoWkHAgIDAQEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5zaWVt
|
||||
ZW5zLmNvbS9wa2kvMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUvb0qQyI9SEpX
|
||||
fpgxF6lwne6fqJkwggEEBggrBgEFBQcBAQSB9zCB9DAyBggrBgEFBQcwAoYmaHR0
|
||||
cDovL2FoLnNpZW1lbnMuY29tL3BraT9aWlpaWlpBMi5jcnQwQQYIKwYBBQUHMAKG
|
||||
NWxkYXA6Ly9hbC5zaWVtZW5zLm5ldC9DTj1aWlpaWlpBMixMPVBLST9jQUNlcnRp
|
||||
ZmljYXRlMEkGCCsGAQUFBzAChj1sZGFwOi8vYWwuc2llbWVucy5jb20vQ049Wlpa
|
||||
WlpaQTIsbz1UcnVzdGNlbnRlcj9jQUNlcnRpZmljYXRlMDAGCCsGAQUFBzABhiRo
|
||||
dHRwOi8vb2NzcC5wa2ktc2VydmljZXMuc2llbWVucy5jb20wDQYJKoZIhvcNAQEL
|
||||
BQADggIBAFY2sbX8DKjKlp0OdH+7Ak21ZdRr6p6JIXzQShWpuFr3wYTpM47+WYVe
|
||||
arBekf8eS08feM+TWw6FHt/VNMpn5fLr20jHn7h+j3ClerAxQbx8J6BxhwJ/4DMy
|
||||
0cCdbe/fpfJyD/8TGdjnxwAgoq9iPuy1ueVnevygnLcuq1+se6EWJm9v1zrwB0LH
|
||||
rE4/NaSCi06+KGg0D9yiigma9yErRZCiaFvqYXUEl7iGpu2OM9o38gZfGzkKaPtQ
|
||||
e9BzRs6ndmvNpQQGLXvOlHn6DIsOuBHJp66A+wumRO2AC8rs1rc4NAIjCFRrz8k1
|
||||
kzb+ibFiTklWG69+At5/nb06BO/0ER4U18sSpmvOsFKNKPXzLkAn8O8ZzB+8afxy
|
||||
egiIJFxYaqoJcQq3CCv8Xp7tp6I+ojr1ui0jK0yqJq6QfgS8FCXIJ+EErNYuoerx
|
||||
ba6amD83e524sdMhCfD5dw6IeEY7LUl465ifUm+v5W3jStfa+0cQXnLZNGsC85nP
|
||||
Lw5cXVIE3LfoSO3kWH45MfcX32fuqmyP2N3k+/+IOfUpSdT1iR1pEu0g/mow7lGj
|
||||
CZngjmMpoto/Qi3l/n1KPWfmB09FZlUhHcGsHbK8+mrkqpv6HW3tKDSorah98aLM
|
||||
Wvu1IXTrU9fOyBqt92i0e5buH+/9NHia0i6k79kwQy5wu6Q21GgUMIIIbDCCBlSg
|
||||
AwIBAgIEL4jNizANBgkqhkiG9w0BAQsFADCBmTELMAkGA1UEBhMCREUxDzANBgNV
|
||||
BAgMBkJheWVybjERMA8GA1UEBwwITXVlbmNoZW4xEDAOBgNVBAoMB1NpZW1lbnMx
|
||||
ETAPBgNVBAUTCFpaWlpaWkExMR0wGwYDVQQLDBRTaWVtZW5zIFRydXN0IENlbnRl
|
||||
cjEiMCAGA1UEAwwZU2llbWVucyBSb290IENBIFYzLjAgMjAxNjAeFw0xNjA3MjAx
|
||||
MzA5MDhaFw0yMjA3MjAxMzA5MDhaMIGfMQswCQYDVQQGEwJERTEPMA0GA1UECAwG
|
||||
QmF5ZXJuMREwDwYDVQQHDAhNdWVuY2hlbjEQMA4GA1UECgwHU2llbWVuczERMA8G
|
||||
A1UEBRMIWlpaWlpaQTIxHTAbBgNVBAsMFFNpZW1lbnMgVHJ1c3QgQ2VudGVyMSgw
|
||||
JgYDVQQDDB9TaWVtZW5zIElzc3VpbmcgQ0EgRUUgQXV0aCAyMDE2MIICIjANBgkq
|
||||
hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAy1aUq88DjZYPge0vZnAr3KJHmMi0o5mp
|
||||
hy54Xr592Vtf8u/B3TCyD+iGCYANPYUq4sG18qXcVxGadz7zeEm6RI7jKKl3URAv
|
||||
zFGiYForZE0JKxwo956T/diLLpH1vHEQDbp8AjNK7aGoltZnm/Jn6IVQy9iBY0SE
|
||||
lRIBhUlppS4/J2PHtKEvQVYJfkAwTtHuGpvPaesoJ8bHA0KhEZ4+/kIYQebaNDf0
|
||||
ltTmXd4Z8zeUhE25d9MzoFnQUg+F01ewMfc0OsEFheKWP6dmo0MSLWARXxjI3K2R
|
||||
THtJU5hxjb/+SA2wlfpqwNIAkTECDBfqYxHReAT8PeezvzEkNZ9RrXl9qj0Cm2iZ
|
||||
AjY1SL+asuxrGvFwEW/ZKJ2ARY/ot1cHh/I79srzh/jFieShVHbT6s6fyKXmkUjB
|
||||
OEnybUKUqcvNuOXnwEiJ/9jKT5UVBWTDxbEQucAarVNFBEf557o9ievbT+VAZKZ8
|
||||
F4tJge6jl2y19eppflresr7Xui9wekK2LYcLOF3X/MOCFq/9VyQDyE7X9KNGtEx7
|
||||
4V6J2QpbbRJryvavh3b0eQEtqDc65eiEaP8awqOErN8EEYh7Gdx4Um3QFcm1TBhk
|
||||
ZTdQdLlWv4LvIBnXiBEWRczQYEIm5wv5ZkyPwdL39Xwc72esPPBu8FtQFVcQlRdG
|
||||
I2t5Ywefq48CAwEAAaOCArIwggKuMIIBBQYIKwYBBQUHAQEEgfgwgfUwQQYIKwYB
|
||||
BQUHMAKGNWxkYXA6Ly9hbC5zaWVtZW5zLm5ldC9DTj1aWlpaWlpBMSxMPVBLST9j
|
||||
QUNlcnRpZmljYXRlMDIGCCsGAQUFBzAChiZodHRwOi8vYWguc2llbWVucy5jb20v
|
||||
cGtpP1paWlpaWkExLmNydDBKBggrBgEFBQcwAoY+bGRhcDovL2FsLnNpZW1lbnMu
|
||||
Y29tL3VpZD1aWlpaWlpBMSxvPVRydXN0Y2VudGVyP2NBQ2VydGlmaWNhdGUwMAYI
|
||||
KwYBBQUHMAGGJGh0dHA6Ly9vY3NwLnBraS1zZXJ2aWNlcy5zaWVtZW5zLmNvbTAf
|
||||
BgNVHSMEGDAWgBRwbaBQ7KnQLGedGRX+/QRzNcPi1DASBgNVHRMBAf8ECDAGAQH/
|
||||
AgEAMEAGA1UdIAQ5MDcwNQYIKwYBBAGhaQcwKTAnBggrBgEFBQcCARYbaHR0cDov
|
||||
L3d3dy5zaWVtZW5zLmNvbS9wa2kvMIHHBgNVHR8Egb8wgbwwgbmggbaggbOGP2xk
|
||||
YXA6Ly9jbC5zaWVtZW5zLm5ldC9DTj1aWlpaWlpBMSxMPVBLST9hdXRob3JpdHlS
|
||||
ZXZvY2F0aW9uTGlzdIYmaHR0cDovL2NoLnNpZW1lbnMuY29tL3BraT9aWlpaWlpB
|
||||
MS5jcmyGSGxkYXA6Ly9jbC5zaWVtZW5zLmNvbS91aWQ9WlpaWlpaQTEsbz1UcnVz
|
||||
dGNlbnRlcj9hdXRob3JpdHlSZXZvY2F0aW9uTGlzdDAzBgNVHSUELDAqBggrBgEF
|
||||
BQcDAgYIKwYBBQUHAwQGCisGAQQBgjcUAgIGCCsGAQUFBwMJMA4GA1UdDwEB/wQE
|
||||
AwIBBjAdBgNVHQ4EFgQUvb0qQyI9SEpXfpgxF6lwne6fqJkwDQYJKoZIhvcNAQEL
|
||||
BQADggIBAEQB0qDUmU8rX9KVJA/0zxJUmIeE9zeldih8TKrf4UNzS1+2Cqn4agO7
|
||||
MxRG1d52/pL4uKenffwwYy2dP912PwLjCDOL7jvojjQKx/qpVUXF7XWsg8hAQec3
|
||||
7Ras/jGPcPQ3OehbkcKcmXI4MrF0Haqo3q1n29gjlJ0fGn2fF1/CBnybPuODAjWG
|
||||
o9mZodXfz0woGSxkftC6nTmAV2GCvIU+j5hNKpzEzo8c1KwLVeXtB4PAqioRW1BX
|
||||
Ngjc7HQbvX/C39RnpOM3RdITw2KKXFxeKBMXdiDuFz/2CzO8HxKH9EVWEcSRbTnd
|
||||
E5iEB4CZzcvfzl9X5AwrKkiIziOiEoiv21ooWeFWfR9V2dgYIE7G1TFwsQ4p0/w5
|
||||
xBHSzqP8TCJp1MQTw42/t8uUXoFEGqk5FKQWoIaFf7N//FLAn8r+7vxNhF5s+tMl
|
||||
VsdKnXn3q8THB3JSnbb/AWGL9rjPK3vh2d3c0I5cWuKXexPLp74ynl2XUbiOXKE7
|
||||
XPUZ9qgK0G9JrrFMm4x1aID9Y9jqYeEz6krYjdFHo5BOVGso6SqWVJE48TxJ5KVv
|
||||
FUb4OxhOAw118Tco0XA7H1G3c2/AKJvIku3cRuj8eLe/cpKqUqQl8uikIZs7POaO
|
||||
+9eJsOnNPmUiwumJgwAo3Ka4ALteKZLbGmKvuo/2ueKCQ29F5rnOMYICOzCCAjcC
|
||||
AQEwgagwgZ8xCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCYXllcm4xETAPBgNVBAcM
|
||||
CE11ZW5jaGVuMRAwDgYDVQQKDAdTaWVtZW5zMREwDwYDVQQFEwhaWlpaWlpBMjEd
|
||||
MBsGA1UECwwUU2llbWVucyBUcnVzdCBDZW50ZXIxKDAmBgNVBAMMH1NpZW1lbnMg
|
||||
SXNzdWluZyBDQSBFRSBBdXRoIDIwMTYCBGvn1/4wCwYJYIZIAWUDBAIBoGkwHAYJ
|
||||
KoZIhvcNAQkFMQ8XDTE5MDYyMDEwNDIwNlowLwYJKoZIhvcNAQkEMSIEIHPHp00z
|
||||
IZ93dAl/uwOnixzuAtf1fUTyxFFaq/5yzc+0MBgGCSqGSIb3DQEJAzELBgkqhkiG
|
||||
9w0BBwEwCwYJKoZIhvcNAQEBBIIBAD8Or5F/A/vpeNPv1YOrGzTrMU5pbn6o8t2+
|
||||
Hqn+hAdjbD26HqjYQN/nyXNBpgXiV4P5vEVNVpmViAAXGsWKM3BJx7GdH/uUwDnj
|
||||
upvoViXYtzQ92UC2Xzqo7uOg2ryMbDIFNfLosvy4a7NfDLYoMsVYrgOKpDrfOLsS
|
||||
1VNUjlyftm7vKigkJLrPIEmXrZSVEqsdKvFhcSxS55lm0lVd/fTCAi7TXR2FZWbc
|
||||
TrsTrZx2YdIJDwN04szzBjnQ7yJ4jBLYz1GMBe22xDD10UA4XdBYK07rkcabrv/t
|
||||
kUMI7uN/KeiKPeSvWCn3AUqH6TIFa9WU+tI4U2A2BsUMn6Bq9TY=
|
||||
-----END SIGNED MESSAGE-----
|
||||
SIGNATURE
|
||||
end
|
||||
|
||||
def signed_commit_base_data
|
||||
<<~SIGNEDDATA
|
||||
tree 84c167013d2ee86e8a88ac6011df0b178d261a23
|
||||
parent e63f41fe459e62e1228fcef60d7189127aeba95a
|
||||
author Roger Meier <r.meier@siemens.com> 1561027326 +0200
|
||||
committer Roger Meier <r.meier@siemens.com> 1561027326 +0200
|
||||
|
||||
feat: add a smime signed commit
|
||||
SIGNEDDATA
|
||||
end
|
||||
|
||||
def certificate_crl
|
||||
'http://ch.siemens.com/pki?ZZZZZZA2.crl'
|
||||
end
|
||||
|
||||
def certificate_serial
|
||||
1810356222
|
||||
end
|
||||
|
||||
def certificate_subject_key_identifier
|
||||
'EC:00:B5:28:02:5C:D3:A5:A1:AB:C2:A1:34:81:84:AA:BF:9B:CF:F8'
|
||||
end
|
||||
|
||||
def issuer_subject_key_identifier
|
||||
'BD:BD:2A:43:22:3D:48:4A:57:7E:98:31:17:A9:70:9D:EE:9F:A8:99'
|
||||
end
|
||||
|
||||
def certificate_email
|
||||
'r.meier@siemens.com'
|
||||
end
|
||||
|
||||
def certificate_issuer
|
||||
'CN=Siemens Issuing CA EE Auth 2016,OU=Siemens Trust Center,serialNumber=ZZZZZZA2,O=Siemens,L=Muenchen,ST=Bayern,C=DE'
|
||||
end
|
||||
|
||||
def certificate_subject
|
||||
'CN=Meier Roger,O=Siemens,SN=Meier,GN=Roger,serialNumber=Z000NWDH'
|
||||
end
|
||||
|
||||
def names
|
||||
['Roger Meier']
|
||||
end
|
||||
|
||||
def emails
|
||||
['r.meier@siemens.com']
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe CreateGpgSignatureWorker do
|
||||
describe CreateCommitSignatureWorker do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:commits) { project.repository.commits('HEAD', limit: 3).commits }
|
||||
let(:commit_shas) { commits.map(&:id) }
|
||||
let(:gpg_commit) { instance_double(Gitlab::Gpg::Commit) }
|
||||
let(:x509_commit) { instance_double(Gitlab::X509::Commit) }
|
||||
|
||||
context 'when GpgKey is found' do
|
||||
context 'when a signature is found' do
|
||||
before do
|
||||
allow(Project).to receive(:find_by).with(id: project.id).and_return(project)
|
||||
allow(project).to receive(:commits_by).with(oids: commit_shas).and_return(commits)
|
||||
|
@ -18,6 +19,7 @@ describe CreateGpgSignatureWorker do
|
|||
|
||||
it 'calls Gitlab::Gpg::Commit#signature' do
|
||||
commits.each do |commit|
|
||||
allow(commit).to receive(:signature_type).and_return(:PGP)
|
||||
expect(Gitlab::Gpg::Commit).to receive(:new).with(commit).and_return(gpg_commit).once
|
||||
end
|
||||
|
||||
|
@ -31,13 +33,46 @@ describe CreateGpgSignatureWorker do
|
|||
allow(Gitlab::Gpg::Commit).to receive(:new).and_return(gpg_commit)
|
||||
allow(Gitlab::Gpg::Commit).to receive(:new).with(commits.first).and_raise(StandardError)
|
||||
|
||||
allow(commits[1]).to receive(:signature_type).and_return(:PGP)
|
||||
allow(commits[2]).to receive(:signature_type).and_return(:PGP)
|
||||
|
||||
expect(gpg_commit).to receive(:signature).twice
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
it 'calls Gitlab::X509::Commit#signature' do
|
||||
commits.each do |commit|
|
||||
allow(commit).to receive(:signature_type).and_return(:X509)
|
||||
expect(Gitlab::X509::Commit).to receive(:new).with(commit).and_return(x509_commit).once
|
||||
end
|
||||
|
||||
expect(x509_commit).to receive(:signature).exactly(commits.size).times
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
it 'can recover from exception and continue the X509 signature process' do
|
||||
allow(x509_commit).to receive(:signature)
|
||||
allow(Gitlab::X509::Commit).to receive(:new).and_return(x509_commit)
|
||||
allow(Gitlab::X509::Commit).to receive(:new).with(commits.first).and_raise(StandardError)
|
||||
|
||||
allow(commits[1]).to receive(:signature_type).and_return(:X509)
|
||||
allow(commits[2]).to receive(:signature_type).and_return(:X509)
|
||||
|
||||
expect(x509_commit).to receive(:signature).twice
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'handles when a string is passed in for the commit SHA' do
|
||||
before do
|
||||
allow(Project).to receive(:find_by).with(id: project.id).and_return(project)
|
||||
allow(project).to receive(:commits_by).with(oids: Array(commit_shas.first)).and_return(commits)
|
||||
allow(commits.first).to receive(:signature_type).and_return(:PGP)
|
||||
end
|
||||
|
||||
it 'creates a signature once' do
|
||||
allow(Gitlab::Gpg::Commit).to receive(:new).with(commits.first).and_return(gpg_commit)
|
||||
|
||||
|
@ -67,5 +102,11 @@ describe CreateGpgSignatureWorker do
|
|||
|
||||
described_class.new.perform(commit_shas, nonexisting_project_id)
|
||||
end
|
||||
|
||||
it 'does not call Gitlab::X509::Commit#signature' do
|
||||
expect_any_instance_of(Gitlab::X509::Commit).not_to receive(:signature)
|
||||
|
||||
described_class.new.perform(commit_shas, nonexisting_project_id)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -48,7 +48,7 @@ elasticsearch-curator:
|
|||
1:
|
||||
action: delete_indices
|
||||
description: >-
|
||||
Delete indices older than 15 days (based on index name), for filebeat-
|
||||
Delete indices older than 30 days (based on index name), for filebeat-
|
||||
prefixed indices. Ignore the error if the filter does not result in an
|
||||
actionable list of indices (ignore_empty_list) and exit cleanly.
|
||||
options:
|
||||
|
@ -62,7 +62,7 @@ elasticsearch-curator:
|
|||
direction: older
|
||||
timestring: '%Y.%m.%d'
|
||||
unit: days
|
||||
unit_count: 15
|
||||
unit_count: 30
|
||||
|
||||
elasticsearch-exporter:
|
||||
enabled: false
|
||||
|
|
Loading…
Reference in New Issue