From b915ed726529e28a4e1f25fa7def9b4ba56fab5c Mon Sep 17 00:00:00 2001 From: Joe Rafaniello Date: Wed, 11 Dec 2019 17:18:04 -0500 Subject: [PATCH] Merge pull request #266 from carbonin/readd_selinux_changes_for_cockpit Add selinux changes for cockpit (cherry picked from commit 2d4ef11e7144313970cd1a1f97ffd80eed409d49) Fixes https://bugzilla.redhat.com/show_bug.cgi?id=1784555 --- LINK/usr/bin/cockpit-auth-miq | 274 ++++++++++++++++++++++++++++++++++ manageiq-setup.sh | 33 +++- 2 files changed, 306 insertions(+), 1 deletion(-) create mode 100755 LINK/usr/bin/cockpit-auth-miq diff --git a/LINK/usr/bin/cockpit-auth-miq b/LINK/usr/bin/cockpit-auth-miq new file mode 100755 index 0000000..908d698 --- /dev/null +++ b/LINK/usr/bin/cockpit-auth-miq @@ -0,0 +1,274 @@ +#!/usr/bin/env ruby +require "net/http" +require "json" +require "uri" +require 'socket' +require 'drb/drb' +require 'time' +require 'base64' +require 'securerandom' + +MAX_AUTH_SIZE = 64 * 1024 +AUTH_FD = 10 + +def send_auth_command(challenge, response, data) + cmd = { "command" => "authorize" } + cmd = cmd.merge(data) unless data.nil? + + unless challenge.nil? + timestamp = Time.now.to_i + pid = Process.pid + cmd["cookie"] = "session#{pid}#{timestamp}" + cmd["challenge"] = challenge + end + + unless response.nil? + cmd["response"] = response + end + + text = JSON.dump(cmd) + size = text.length + 1 + $stdout.write("#{size}\n\n#{text}") + $stdout.flush +end + +def send_problem_init(problem, message, auth_result) + cmd = { + "command" => "init", + "problem" => problem + } + + unless message.nil? + cmd["message"] = message + end + + unless auth_result.nil? + cmd["auth-method-results"] = auth_result + end + + text = JSON.dump(cmd) + size = text.length + 1 + $stdout.write("#{size}\n\n#{text}") + $stdout.flush +end + +def read_size(io) + size = 0 + seen = 0 + + while seen < 8 + t = io.readpartial(1) + + unless t + return 0 + end + + if t == "\n" + break + end + + if t.to_i.zero? && t != "0" + raise ArgumentError("Invalid frame: invalid size") + end + + size = (size * 10) + t.to_i + seen += 1 + end + + if seen == 8 + raise ArgumentError("Invalid frame: size too long") + end + + size +end + +def read_frame(fd) + io = IO.for_fd(fd) + size = read_size(io) + + data = "" + while size > 0 + d = io.readpartial(1) + size -= d.length + data += d + end + data +end + +def read_auth_reply + data = read_frame(1) + cmd = JSON.parse(data) + if cmd["command"] != "authorize" || !cmd["cookie"] || !cmd["response"] + raise ArgumentError("Did not receive a valid authorize command") + end + + cmd["response"] +end + +def fetch_authorize_token + send_auth_command("*", nil, nil) + begin + data = read_auth_reply + parts = data.split(' ', 2) + token = parts[1] + rescue ArgumentError => e + send_problem_init("internal-error", e.to_s, nil) + raise + rescue JSON::ParserError => e + send_problem_init("internal-error", e.to_s, nil) + raise + end + token || "" +end + +def launch_ssh_with_auth_fd(ios, ssh_command, host, user, password, key) + s1, s2 = UNIXSocket.socketpair(Socket::SOCK_SEQPACKET) + s2.syswrite(key ? key : password) + + trap("CHLD", "IGNORE") + + fork do + s1.close + loop do + begin + # Read from cockpit-ssh write to cockpit-ws + data, _ad = s2.recvfrom(MAX_AUTH_SIZE) + break unless data + ios.syswrite(data) + + # Read from cockpit-ws write to cockpit-ssh + data = ios.readpartial(MAX_AUTH_SIZE) + break unless data + s2.send(data, 0) + rescue IOError + break + end + end + + # Sockets may already be closed + begin + ios.close + rescue IOError + end + + begin + s2.close + rescue IOError + end + end + + s2.close + host = "#{user}@#{host}" + env = { + "COCKPIT_SSH_ALLOW_UNKNOWN" => '1', + "COCKPIT_SSH_SUPPORTS_HOST_KEY_PROMPT" => '1', + "COCKPIT_AUTH_MESSAGE_TYPE" => key ? 'private-key' : 'password' + } + + exec(env, ssh_command, host, + 3 => s1, 0 => $stdin, 1 => $stdout, 2 => $stderr) +end + +def launch_ssh_with_authorize_cmd(ssh_command, host, user, password, key) + env = { + "COCKPIT_SSH_ALLOW_UNKNOWN" => '1' + } + + host = "#{user}@#{host}" + + auth_data = 'Basic ' + Base64.encode64("#{user}:#{password}").chomp unless password.nil? + auth_data = "host-key #{key}" unless key.nil? + send_auth_command(nil, auth_data, nil) + exec(env, ssh_command, host, 0 => $stdin, 1 => $stdout, 2 => $stderr) +end + +def prompt_for_data(ios, json_req) + if ios.nil? + prompt = Base64.encode64(json_req['prompt']).chomp + id = SecureRandom.uuid + send_auth_command("x-conversation #{id} #{prompt}", nil, json_req) + data = read_auth_reply + parts = data.split(' ', 3) + if parts[0].downcase != "x-conversation" || parts[1] != id || !parts[2] + send_error(nil, + "error" => "internal-error", + "message" => "invalid conversation response") + end + Base64.decode64(parts[2]) + else + ios.syswrite(JSON.dump(json_req)) + ios.readpartial(MAX_AUTH_SIZE) + end +end + +def send_error(ios, error_json) + if ios.nil? + send_problem_init(error_json["error"], error_json["message"], + error_json["auth-method-results"]) + else + ios.syswrite(json.dump(error_json)) + end + exit 1 +end + +def launch_ssh(ios, ssh_command, host, user, password, key) + if ios.nil? + launch_ssh_with_authorize_cmd(ssh_command, host, user, password, key) + else + launch_ssh_with_auth_fd(ios, ssh_command, host, user, password, key) + end +end + +# Older cockpit-ws versions use a special auth fd +# for authentication data. Newer versions use the +# cockpit protocol on stdin/stdout. +# We want to support both so try to talk AUTH_FD +# first if that fails switch to stdin/stdout +host = ARGV[-1] +begin + ios = IO.for_fd(AUTH_FD) +rescue Errno::EBADF + ios = nil +end + +token = fetch_authorize_token if ios.nil? +token = ios.readpartial(MAX_AUTH_SIZE) unless ios.nil? + +begin + cockpit_drb = DRbObject.new_with_uri(ENV["DRB_URI"]) + results = cockpit_drb.authenticate_for_host(token, host) + ssh_command = cockpit_drb.ssh_command + + if !results[:valid] + send_error(ios, + "error" => "authentication-failed", + "message" => "Token was not valid", + "auth-method-results" => { "password" => "not-tried", "token" => "denied" }) + + elsif !results[:known] + send_error(ios, "error" => "unknown-host") + end +rescue => err + send_error(ios, + "error" => "internal-error", + "message" => "Couldn't validate token: #{err}") +end + +user = results[:userid] +password = results[:password] +key = results[:key] + +# Prompt for user if we didn't get one +unless user + user = prompt_for_data(ios, + 'prompt' => 'Please provide a username:', + 'echo' => 1, + 'message' => 'Unable to determine the user to connect as.') +end + +# Prompt for password if we didn't get one +if !password && !key + password = prompt_for_data(ios, 'prompt' => 'Password:') +end + +launch_ssh(ios, ssh_command, host, user, password, key) diff --git a/manageiq-setup.sh b/manageiq-setup.sh index d9b36ed..4c5aa39 100755 --- a/manageiq-setup.sh +++ b/manageiq-setup.sh @@ -21,7 +21,38 @@ EOF /usr/sbin/semanage fcontext -a -t httpd_log_t "/var/www/miq/vmdb/log(/.*)?" /usr/sbin/semanage fcontext -a -t cert_t "/var/www/miq/vmdb/certs(/.*)?" /usr/sbin/semanage fcontext -a -t logrotate_exec_t ${APPLIANCE_SOURCE_DIRECTORY}/logrotate_free_space_check.sh -/usr/sbin/semanage fcontext -a -t etc_t "/var/www/miq/vmdb/config/cockpit" +/usr/sbin/semanage fcontext -a -t etc_t "/var/www/miq/vmdb/config/cockpit(/.*)?" + +cat <<'EOF' > /tmp/cockpit_ws_miq.te +module cockpit_ws_miq 1.0; + +require { + type unreserved_port_t; + type cockpit_session_t; + type httpd_sys_content_t; + type initrc_t; + type cockpit_ws_t; + type initrc_tmp_t; + class tcp_socket name_bind; + class sock_file write; + class unix_stream_socket connectto; + class dir search; +} + +#============= cockpit_session_t ============== +allow cockpit_session_t httpd_sys_content_t:dir search; + +#============= cockpit_ws_t ============== +allow cockpit_ws_t initrc_t:unix_stream_socket connectto; +allow cockpit_ws_t initrc_tmp_t:sock_file write; + +#!!!! This avc can be allowed using the boolean 'nis_enabled' +allow cockpit_ws_t unreserved_port_t:tcp_socket name_bind; +EOF + +checkmodule -M -m -o /tmp/cockpit_ws_miq.mod /tmp/cockpit_ws_miq.te +semodule_package -o /tmp/cockpit_ws_miq.pp -m /tmp/cockpit_ws_miq.mod +semodule -i /tmp/cockpit_ws_miq.pp [ -x /sbin/restorecon ] && /sbin/restorecon -R -v /var/www/miq/vmdb/log [ -x /sbin/restorecon ] && /sbin/restorecon -R -v /etc/sysconfig