6781: Add arvados-login-sync (was update-shell-accounts.rb)
[arvados.git] / services / login-sync / bin / arvados-login-sync
1 #!/usr/bin/env ruby
2
3 require 'rubygems'
4 require 'pp'
5 require 'arvados'
6 require 'etc'
7 require 'fileutils'
8 require 'yaml'
9
10 # This script does the actual account/key management on disk for the shell machine(s).
11 #
12 # Ward Vandewege <ward@curoverse.com>
13
14 # Default is development
15 production = ARGV[0] == "production"
16
17 ENV["RAILS_ENV"] = "development"
18 ENV["RAILS_ENV"] = "production" if production
19
20 DEBUG = 1
21
22 # load and merge in the environment-specific application config info
23 # if present, overriding base config parameters as specified
24 path = File.dirname(__FILE__) + '/config/arvados-clients.yml'
25 if File.exists?(path) then
26         cp_config = YAML.load_file(path)[ENV['RAILS_ENV']]
27 else
28         puts "Please create a\n " + File.dirname(__FILE__) + "/config/arvados-clients.yml\n file"
29         exit 1
30 end
31
32 shell_hostname = cp_config['arvados_shell_hostname']
33
34 ENV['ARVADOS_API_HOST'] = cp_config['arvados_api_host']
35 ENV['ARVADOS_API_TOKEN'] = cp_config['arvados_api_token']
36
37 keys = ''
38
39 seen = Hash.new
40
41 begin
42
43   uids = Hash[Etc.to_enum(:passwd).map { |ent| [ent.name, ent.uid] }]
44   gids = Hash[Etc.to_enum(:group).map { |ent| [ent.name, ent.gid] }]
45   arv = Arvados.new( { :suppress_ssl_warnings => false } )
46
47   begin
48     logins = arv.virtual_machine.get_all_logins(limit: 10000, uuid: cp_config['vm_uuid'])[:items]
49   rescue
50     logins = arv.virtual_machine.logins(:uuid => cp_config['vm_uuid'])[:items]
51   end
52   logins = [] if logins.nil?
53   logins = logins.reject { |l| l[:username].nil? or l[:hostname].nil? or l[:public_key].nil? or l[:hostname] != shell_hostname }
54
55   # No system users
56   uid_min = 1000
57   open("/etc/login.defs", encoding: "utf-8") do |login_defs|
58     login_defs.each_line do |line|
59       next unless match = /^UID_MIN\s+(\S+)$/.match(line)
60       if match[1].start_with?("0x")
61         base = 16
62       elsif match[1].start_with?("0")
63         base = 8
64       else
65         base = 10
66       end
67       new_uid_min = match[1].to_i(base)
68       uid_min = new_uid_min if (new_uid_min > 0)
69     end
70   end
71   logins.reject! { |l| (uids[l[:username]] || 65535) < uid_min }
72
73   keys = Hash.new()
74
75   # Collect all keys
76   logins.each do |l|
77     keys[l[:username]] = Array.new() if not keys.has_key?(l[:username])
78     key = l[:public_key]
79     # Handle putty-style ssh public keys
80     key.sub!(/^(Comment: "r[^\n]*\n)(.*)$/m,'ssh-rsa \2 \1')
81     key.sub!(/^(Comment: "d[^\n]*\n)(.*)$/m,'ssh-dss \2 \1')
82     key.gsub!(/\n/,'')
83     key.strip
84
85     keys[l[:username]].push(key) if not keys[l[:username]].include?(key)
86   end
87
88   seen = Hash.new()
89   devnull = open("/dev/null", "w")
90
91   logins.each do |l|
92     next if seen[l[:username]]
93     seen[l[:username]] = true if not seen.has_key?(l[:username])
94     @homedir = "/home/#{l[:username]}"
95
96     unless uids[l[:username]]
97       STDERR.puts "Creating account #{l[:username]}"
98       groups = l[:groups] || []
99       # Adding users to the FUSE group has long been hardcoded behavior.
100       groups << "fuse"
101       groups.select! { |name| gids[name] }
102       # Create new user
103       next unless system("/usr/sbin/useradd", "-m",
104                          "-c", l[:username],
105                          "-s", "/bin/bash",
106                          "-G", groups.join(","),
107                          l[:username],
108                          out: devnull)
109     end
110     # Create .ssh directory if necessary
111     userdotssh = File.join(@homedir, ".ssh")
112     Dir.mkdir(userdotssh) if !File.exists?(userdotssh)
113     @key = "#######################################################################################
114 #  THIS FILE IS MANAGED BY #{$0} -- CHANGES WILL BE OVERWRITTEN  #
115 #######################################################################################\n\n"
116     @key += keys[l[:username]].join("\n") + "\n"
117     userauthkeys = File.join(userdotssh, "authorized_keys")
118     if !File.exists?(userauthkeys) or IO::read(userauthkeys) != @key then
119       f = File.new(userauthkeys, 'w')
120       f.write(@key)
121       f.close()
122     end
123     FileUtils.chown_R(l[:username], l[:username], userdotssh)
124     File.chmod(0700, userdotssh)
125     File.chmod(0750, @homedir)
126   end
127
128   devnull.close
129 rescue Exception => bang
130   puts "Error: " + bang.to_s
131   puts bang.backtrace.join("\n")
132   exit 1
133 end
134