diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/action/api_2fa_recovery.rb | 12 | ||||
| -rw-r--r-- | lib/action/git_lfs_authenticate.rb | 10 | ||||
| -rw-r--r-- | lib/actor.rb | 3 | ||||
| -rw-r--r-- | lib/actor/base.rb | 4 | ||||
| -rw-r--r-- | lib/actor/key.rb | 2 | ||||
| -rw-r--r-- | lib/actor/username.rb | 19 | ||||
| -rw-r--r-- | lib/gitlab_keys.rb | 26 | ||||
| -rw-r--r-- | lib/gitlab_net.rb | 17 | ||||
| -rw-r--r-- | lib/gitlab_shell.rb | 26 |
9 files changed, 81 insertions, 38 deletions
diff --git a/lib/action/api_2fa_recovery.rb b/lib/action/api_2fa_recovery.rb index 5597ff0..ad8130f 100644 --- a/lib/action/api_2fa_recovery.rb +++ b/lib/action/api_2fa_recovery.rb @@ -3,8 +3,8 @@ require_relative '../gitlab_logger' module Action class API2FARecovery < Base - def initialize(key) - @key = key + def initialize(actor) + @actor = actor end def execute(_, _) @@ -13,7 +13,7 @@ module Action private - attr_reader :key + attr_reader :actor def continue?(question) puts "#{question} (yes/no)" @@ -34,10 +34,10 @@ module Action return end - resp = api.two_factor_recovery_codes(key.key_id) + resp = api.two_factor_recovery_codes(self) if resp['success'] codes = resp['recovery_codes'].join("\n") - $logger.info('API 2FA recovery success', user: key.log_username) + $logger.info('API 2FA recovery success', user: actor.log_username) puts "Your two-factor authentication recovery codes are:\n\n" \ "#{codes}\n\n" \ "During sign in, use one of the codes above when prompted for\n" \ @@ -45,7 +45,7 @@ module Action "a new device so you do not lose access to your account again." true else - $logger.info('API 2FA recovery error', user: key.log_username) + $logger.info('API 2FA recovery error', user: actor.log_username) puts "An error occurred while trying to generate new recovery codes.\n" \ "#{resp['message']}" end diff --git a/lib/action/git_lfs_authenticate.rb b/lib/action/git_lfs_authenticate.rb index 218c71e..d2e6d76 100644 --- a/lib/action/git_lfs_authenticate.rb +++ b/lib/action/git_lfs_authenticate.rb @@ -3,15 +3,15 @@ require_relative '../gitlab_logger' module Action class GitLFSAuthenticate < Base - def initialize(key, repo_name) - @key = key + def initialize(actor, repo_name) + @actor = actor @repo_name = repo_name end def execute(_, _) GitlabMetrics.measure('lfs-authenticate') do - $logger.info('Processing LFS authentication', user: key.log_username) - lfs_access = api.lfs_authenticate(key.key_id, repo_name) + $logger.info('Processing LFS authentication', user: actor.log_username) + lfs_access = api.lfs_authenticate(self, repo_name) return unless lfs_access puts lfs_access.authentication_payload @@ -21,6 +21,6 @@ module Action private - attr_reader :key, :repo_name + attr_reader :actor, :repo_name end end diff --git a/lib/actor.rb b/lib/actor.rb index eab1bc4..4e8b3b8 100644 --- a/lib/actor.rb +++ b/lib/actor.rb @@ -1,6 +1,7 @@ require_relative 'actor/base' require_relative 'actor/key' require_relative 'actor/user' +require_relative 'actor/username' module Actor class UnsupportedActorError < StandardError; end @@ -11,6 +12,8 @@ module Actor Key.from(str, audit_usernames: audit_usernames) when User.id_regex User.from(str, audit_usernames: audit_usernames) + when Username.id_regex + Username.from(str, audit_usernames: audit_usernames) else raise UnsupportedActorError end diff --git a/lib/actor/base.rb b/lib/actor/base.rb index 76cc522..319873f 100644 --- a/lib/actor/base.rb +++ b/lib/actor/base.rb @@ -31,6 +31,10 @@ module Actor "#{self.class.identifier_prefix}-#{id}" end + def identifier_key + self.class.identifier_key + end + def log_username audit_usernames? ? username : "#{klass_name.downcase} with identifier #{identifier}" end diff --git a/lib/actor/key.rb b/lib/actor/key.rb index 411ef15..46f013a 100644 --- a/lib/actor/key.rb +++ b/lib/actor/key.rb @@ -21,7 +21,7 @@ module Actor def username @username ||= begin - user = GitlabNet.new.discover(key_id) + user = GitlabNet.new.discover(self) user ? "@#{user['username']}" : ANONYMOUS_USER end end diff --git a/lib/actor/username.rb b/lib/actor/username.rb new file mode 100644 index 0000000..e0a07dd --- /dev/null +++ b/lib/actor/username.rb @@ -0,0 +1,19 @@ +require_relative 'base' + +module Actor + class Username < Base + alias username identifier + + def self.identifier_prefix + 'username'.freeze + end + + def self.identifier_key + 'username'.freeze + end + + def self.id_regex + /\Ausername\-\d+\Z/ + end + end +end diff --git a/lib/gitlab_keys.rb b/lib/gitlab_keys.rb index 30444c3..3ee2882 100644 --- a/lib/gitlab_keys.rb +++ b/lib/gitlab_keys.rb @@ -9,12 +9,20 @@ class GitlabKeys # rubocop:disable Metrics/ClassLength attr_accessor :auth_file, :key - def self.command(key_id) + def self.command(whatever) + "#{ROOT_PATH}/bin/gitlab-shell #{whatever}" + end + + def self.command_key(key_id) unless /\A[a-z0-9-]+\z/ =~ key_id raise KeyError, "Invalid key_id: #{key_id.inspect}" end - "#{ROOT_PATH}/bin/gitlab-shell #{key_id}" + command(key_id) + end + + def self.whatever_line(command, trailer) + "command=\"#{command}\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty #{trailer}" end def self.key_line(key_id, public_key) @@ -24,7 +32,17 @@ class GitlabKeys # rubocop:disable Metrics/ClassLength raise KeyError, "Invalid public_key: #{public_key.inspect}" end - "command=\"#{command(key_id)}\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty #{public_key}" + whatever_line(command_key(key_id), public_key) + end + + def self.principal_line(username_key_id, principal) + principal.chomp! + + if principal.include?("\n") + raise KeyError, "Invalid principal: #{principal.inspect}" + end + + whatever_line(command_key(username_key_id), principal) end def initialize @@ -119,7 +137,7 @@ class GitlabKeys # rubocop:disable Metrics/ClassLength $logger.info('Removing key', key_id: @key_id) open_auth_file('r+') do |f| while line = f.gets # rubocop:disable Style/AssignmentInCondition - next unless line.start_with?("command=\"#{self.class.command(@key_id)}\"") + next unless line.start_with?("command=\"#{self.class.command_key(@key_id)}\"") f.seek(-line.length, IO::SEEK_CUR) # Overwrite the line with #'s. Because the 'line' variable contains # a terminating '\n', we write line.length - 1 '#' characters. diff --git a/lib/gitlab_net.rb b/lib/gitlab_net.rb index 34e2ff6..0a3e383 100644 --- a/lib/gitlab_net.rb +++ b/lib/gitlab_net.rb @@ -26,22 +26,24 @@ class GitlabNet env: env } - params[actor.class.identifier_key.to_sym] = actor.id + params[actor.identifier_key.to_sym] = actor.id resp = post("#{internal_api_endpoint}/allowed", params) determine_action(actor, resp) end - def discover(key_id) - resp = get("#{internal_api_endpoint}/discover?key_id=#{key_id}") + def discover(actor) + resp = get("#{internal_api_endpoint}/discover?#{actor.identifier_key}=#{actor.id}") JSON.parse(resp.body) rescue JSON::ParserError, ApiUnreachableError nil end - def lfs_authenticate(key_id, repo) - params = { project: sanitize_path(repo), key_id: key_id } + def lfs_authenticate(actor, repo) + params = { project: sanitize_path(repo) } + params[actor.identifier_key.to_sym] = actor.id + resp = post("#{internal_api_endpoint}/lfs_authenticate", params) GitlabLfsAuthentication.build_from_json(resp.body) if resp.code == HTTP_SUCCESS @@ -75,8 +77,9 @@ class GitlabNet nil end - def two_factor_recovery_codes(key_id) - resp = post("#{internal_api_endpoint}/two_factor_recovery_codes", key_id: key_id) + def two_factor_recovery_codes(actor) + params = { actor.identifier_key.to_sym => actor.id } + resp = post("#{internal_api_endpoint}/two_factor_recovery_codes", params) JSON.parse(resp.body) if resp.code == HTTP_SUCCESS rescue {} diff --git a/lib/gitlab_shell.rb b/lib/gitlab_shell.rb index 68d39bc..7b2b2b1 100644 --- a/lib/gitlab_shell.rb +++ b/lib/gitlab_shell.rb @@ -3,7 +3,7 @@ require 'pathname' require_relative 'gitlab_net' require_relative 'gitlab_metrics' -require_relative 'actor/key' +require_relative 'actor' class GitlabShell API_2FA_RECOVERY_CODES_COMMAND = '2fa_recovery_codes'.freeze @@ -18,9 +18,9 @@ class GitlabShell Struct.new('ParsedCommand', :command, :git_access_command, :repo_name, :args) - def initialize(key_str) - @key_str = key_str + def initialize(who) @config = GitlabConfig.new + @actor = Actor.new_from(who, audit_usernames: @config.audit_usernames) end # The origin_cmd variable contains UNTRUSTED input. If the user ran @@ -28,22 +28,22 @@ class GitlabShell # 'evil command'. def exec(origin_cmd) if !origin_cmd || origin_cmd.empty? - puts "Welcome to GitLab, #{key.username}!" + puts "Welcome to GitLab, #{actor.username}!" return true end parsed_command = parse_cmd(origin_cmd) - action = determine_action(parsed_command) + action = determine_action(parsed_command) # FIXME: watch out action.execute(parsed_command.command, parsed_command.args) rescue GitlabNet::ApiUnreachableError $stderr.puts "GitLab: Failed to authorize your Git request: internal API unreachable" false rescue AccessDeniedError, UnknownError => ex - $logger.warn('Access denied', command: origin_cmd, user: key.log_username) + $logger.warn('Access denied', command: origin_cmd, user: actor.log_username) $stderr.puts "GitLab: #{ex.message}" false rescue DisallowedCommandError - $logger.warn('Denied disallowed command', command: origin_cmd, user: key.log_username) + $logger.warn('Denied disallowed command', command: origin_cmd, user: actor.log_username) $stderr.puts 'GitLab: Disallowed command' false rescue InvalidRepositoryPathError @@ -53,11 +53,7 @@ class GitlabShell private - attr_reader :config, :key_str - - def key - @key ||= Actor::Key.from(key_str, audit_usernames: config.audit_usernames) - end + attr_reader :config, :actor def parse_cmd(cmd) args = Shellwords.shellwords(cmd) @@ -99,7 +95,7 @@ class GitlabShell end def determine_action(parsed_command) - return Action::API2FARecovery.new(key) if parsed_command.command == API_2FA_RECOVERY_CODES_COMMAND + return Action::API2FARecovery.new(actor) if parsed_command.command == API_2FA_RECOVERY_CODES_COMMAND GitlabMetrics.measure('verify-access') do # GitlatNet#check_access will raise exception in the event of a problem @@ -107,13 +103,13 @@ class GitlabShell parsed_command.git_access_command, nil, parsed_command.repo_name, - key, + actor, '_any' ) case parsed_command.command when GIT_LFS_AUTHENTICATE_COMMAND - Action::GitLFSAuthenticate.new(key, parsed_command.repo_name) + Action::GitLFSAuthenticate.new(actor, parsed_command.repo_name) else initial_action end |
