X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/73812c5a32581a7f72922dcd2e448e4d4ba4d918..8f8329e7d99e9d1c0e753fb26bf4dc1e76828017:/services/login-sync/bin/arvados-login-sync diff --git a/services/login-sync/bin/arvados-login-sync b/services/login-sync/bin/arvados-login-sync index e1b8c484f0..8162e22a2f 100755 --- a/services/login-sync/bin/arvados-login-sync +++ b/services/login-sync/bin/arvados-login-sync @@ -1,4 +1,7 @@ #!/usr/bin/env ruby +# Copyright (C) The Arvados Authors. All rights reserved. +# +# SPDX-License-Identifier: AGPL-3.0 require 'rubygems' require 'pp' @@ -14,20 +17,26 @@ req_envs.each do |k| end end -keys = '' +exclusive_mode = ARGV.index("--exclusive") +exclusive_banner = "####################################################################################### +# THIS FILE IS MANAGED BY #{$0} -- CHANGES WILL BE OVERWRITTEN # +#######################################################################################\n\n" +start_banner = "### BEGIN Arvados-managed keys -- changes between markers will be overwritten\n" +end_banner = "### END Arvados-managed keys -- changes between markers will be overwritten\n" -seen = Hash.new +# Don't try to create any local accounts +skip_missing_users = ARGV.index("--skip-missing-users") + +keys = '' begin - uids = Hash[Etc.to_enum(:passwd).map { |ent| [ent.name, ent.uid] }] - gids = Hash[Etc.to_enum(:group).map { |ent| [ent.name, ent.gid] }] arv = Arvados.new({ :suppress_ssl_warnings => false }) vm_uuid = ENV['ARVADOS_VIRTUAL_MACHINE_UUID'] logins = arv.virtual_machine.logins(:uuid => vm_uuid)[:items] logins = [] if logins.nil? - logins = logins.reject { |l| l[:username].nil? or l[:hostname].nil? or l[:public_key].nil? or l[:virtual_machine_uuid] != vm_uuid } + logins = logins.reject { |l| l[:username].nil? or l[:hostname].nil? or l[:virtual_machine_uuid] != vm_uuid } # No system users uid_min = 1000 @@ -45,65 +54,164 @@ begin uid_min = new_uid_min if (new_uid_min > 0) end end - logins.reject! { |l| (uids[l[:username]] || 65535) < uid_min } + pwnam = Hash.new() + logins.reject! do |l| + if not pwnam[l[:username]] + begin + pwnam[l[:username]] = Etc.getpwnam(l[:username]) + rescue + if skip_missing_users + STDERR.puts "Account #{l[:username]} not found. Skipping" + true + end + else + if pwnam[l[:username]].uid < uid_min + STDERR.puts "Account #{l[:username]} uid #{pwnam[l[:username]].uid} < uid_min #{uid_min}. Skipping" + true + end + end + end + end keys = Hash.new() # Collect all keys logins.each do |l| keys[l[:username]] = Array.new() if not keys.has_key?(l[:username]) key = l[:public_key] - # Handle putty-style ssh public keys - key.sub!(/^(Comment: "r[^\n]*\n)(.*)$/m,'ssh-rsa \2 \1') - key.sub!(/^(Comment: "d[^\n]*\n)(.*)$/m,'ssh-dss \2 \1') - key.gsub!(/\n/,'') - key.strip + if !key.nil? + # Handle putty-style ssh public keys + key.sub!(/^(Comment: "r[^\n]*\n)(.*)$/m,'ssh-rsa \2 \1') + key.sub!(/^(Comment: "d[^\n]*\n)(.*)$/m,'ssh-dss \2 \1') + key.gsub!(/\n/,'') + key.strip - keys[l[:username]].push(key) if not keys[l[:username]].include?(key) + keys[l[:username]].push(key) if not keys[l[:username]].include?(key) + end end seen = Hash.new() - devnull = open("/dev/null", "w") + + current_user_groups = Hash.new + while (ent = Etc.getgrent()) do + ent.mem.each do |member| + current_user_groups[member] ||= Array.new + current_user_groups[member].push ent.name + end + end + Etc.endgrent() logins.each do |l| next if seen[l[:username]] - seen[l[:username]] = true if not seen.has_key?(l[:username]) + seen[l[:username]] = true + + username = l[:username] - unless uids[l[:username]] + unless pwnam[l[:username]] STDERR.puts "Creating account #{l[:username]}" - groups = l[:groups] || [] - # Adding users to the FUSE group has long been hardcoded behavior. - groups << "fuse" - groups.select! { |name| gids[name] } # Create new user - next unless system("useradd", "-m", - "-c", l[:username], - "-s", "/bin/bash", - "-G", groups.join(","), - l[:username], - out: devnull) + unless system("useradd", "-m", + "-c", username, + "-s", "/bin/bash", + username) + STDERR.puts "Account creation failed for #{l[:username]}: #{$?}" + next + end + begin + pwnam[username] = Etc.getpwnam(username) + rescue => e + STDERR.puts "Created account but then getpwnam() failed for #{l[:username]}: #{e}" + raise + end end - # Create .ssh directory if necessary - @homedir = Etc.getpwnam(l[:username]).dir - userdotssh = File.join(@homedir, ".ssh") - Dir.mkdir(userdotssh) if !File.exists?(userdotssh) - @key = "####################################################################################### -# THIS FILE IS MANAGED BY #{$0} -- CHANGES WILL BE OVERWRITTEN # -#######################################################################################\n\n" - @key += keys[l[:username]].join("\n") + "\n" - userauthkeys = File.join(userdotssh, "authorized_keys") - if !File.exists?(userauthkeys) or IO::read(userauthkeys) != @key then - f = File.new(userauthkeys, 'w') - f.write(@key) + + existing_groups = current_user_groups[username] || [] + groups = l[:groups] || [] + # Adding users to the FUSE group has long been hardcoded behavior. + groups << "fuse" + groups << username + groups.select! { |g| Etc.getgrnam(g) rescue false } + + groups.each do |addgroup| + if existing_groups.index(addgroup).nil? + # User should be in group, but isn't, so add them. + STDERR.puts "Add user #{username} to #{addgroup} group" + system("adduser", username, addgroup) + end + end + + existing_groups.each do |removegroup| + if groups.index(removegroup).nil? + # User is in a group, but shouldn't be, so remove them. + STDERR.puts "Remove user #{username} from #{removegroup} group" + system("deluser", username, removegroup) + end + end + + homedir = pwnam[l[:username]].dir + userdotssh = File.join(homedir, ".ssh") + Dir.mkdir(userdotssh) if !File.exist?(userdotssh) + + newkeys = "###\n###\n" + keys[l[:username]].join("\n") + "\n###\n###\n" + + keysfile = File.join(userdotssh, "authorized_keys") + + if File.exist?(keysfile) + oldkeys = IO::read(keysfile) + else + oldkeys = "" + end + + if exclusive_mode + newkeys = exclusive_banner + newkeys + elsif oldkeys.start_with?(exclusive_banner) + newkeys = start_banner + newkeys + end_banner + elsif (m = /^(.*?\n|)#{start_banner}(.*?\n|)#{end_banner}(.*)/m.match(oldkeys)) + newkeys = m[1] + start_banner + newkeys + end_banner + m[3] + else + newkeys = start_banner + newkeys + end_banner + oldkeys + end + + if oldkeys != newkeys then + f = File.new(keysfile, 'w') + f.write(newkeys) f.close() end - FileUtils.chown_R(l[:username], l[:username], userdotssh) + + userdotconfig = File.join(homedir, ".config") + if !File.exist?(userdotconfig) + Dir.mkdir(userdotconfig) + end + + configarvados = File.join(userdotconfig, "arvados") + Dir.mkdir(configarvados) if !File.exist?(configarvados) + + tokenfile = File.join(configarvados, "settings.conf") + + begin + if !File.exist?(tokenfile) + user_token = arv.api_client_authorization.create(api_client_authorization: {owner_uuid: l[:user_uuid], api_client_id: 0}) + f = File.new(tokenfile, 'w') + f.write("ARVADOS_API_HOST=#{ENV['ARVADOS_API_HOST']}\n") + f.write("ARVADOS_API_TOKEN=v2/#{user_token[:uuid]}/#{user_token[:api_token]}\n") + f.close() + end + rescue => e + STDERR.puts "Error setting token for #{l[:username]}: #{e}" + end + + FileUtils.chown_R(l[:username], nil, userdotssh) + FileUtils.chown_R(l[:username], nil, userdotconfig) File.chmod(0700, userdotssh) - File.chmod(0750, @homedir) - File.chmod(0600, userauthkeys) + File.chmod(0700, userdotconfig) + File.chmod(0700, configarvados) + File.chmod(0750, homedir) + File.chmod(0600, keysfile) + if File.exist?(tokenfile) + File.chmod(0600, tokenfile) + end end - devnull.close rescue Exception => bang puts "Error: " + bang.to_s puts bang.backtrace.join("\n")