# # Copyright:: Copyright (c) 2016 GitLab Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require_relative 'helper' class CertificateHelper include ShellOutHelper def initialize(trusted_cert_dir, omnibus_cert_dir, user_dir) @trusted_certs_dir = trusted_cert_dir @omnibus_certs_dir = omnibus_cert_dir @directory_hash_file = File.join(user_dir, "trusted-certs-directory-hash") end def whitelisted_files [ File.join(@omnibus_certs_dir, "README"), File.join(@omnibus_certs_dir, "cacert.pem") ] end def is_x509_certificate?(file) return false unless valid?(file) begin OpenSSL::X509::Certificate.new(File.read(file)) # DER- or PEM-encoded true rescue OpenSSL::X509::CertificateError => e warn("ERROR: " + file + ": OpenSSL error: " + e.message + "!") false rescue StandardError => e warn(e.message) false end end # If the number of files between the two directories is different # something got added so trigger the run def new_certificate_added? return true unless File.exist?(@directory_hash_file) stored_hash = File.read(@directory_hash_file) trusted_certs_dir_hash != stored_hash end def trusted_certs_dir_hash files = Dir[File.join(@trusted_certs_dir, "*"), File.join(@omnibus_certs_dir, "*")] files_modification_time = files.map { |name| File.stat(name).mtime if valid?(name) } Digest::SHA1.hexdigest(files_modification_time.join) end # Get all files in /opt/gitlab/embedded/ssl/certs # - "cacert.pem", "README" -> ignore # - if valid certificate # - if symlink # - remove broken symlinks # - ignore if pointing to /etc/gitlab/trusted-certs # - ignore because it might be a symlink user created # - else # - copy to trusted-certs dir # - else (not valid) # raise and error def move_existing_certificates Dir.glob(File.join(@omnibus_certs_dir, "*")) do |file| next if !valid?(file) || whitelisted?(file) if is_x509_certificate?(file) move_certificate(file) else raise_msg(file) end end end def whitelisted?(file) whitelisted_files.include?(file) || whitelisted_files.include?(File.realpath(file)) end def valid?(file) exists = File.exist?(file) FileUtils.rm_f(file) if File.symlink?(file) && !exists exists end def move_certificate(file) return if exists_in_trusted?(file) # Move the certs to the trusted certs directory if it is located within our managed certs directory # Otherwise copy the cert to the trusted certs directory realpath = File.realpath(file) if realpath.start_with?(@omnibus_certs_dir) FileUtils.mv(realpath, @trusted_certs_dir, force: true) else FileUtils.cp(realpath, @trusted_certs_dir) end FileUtils.rm_f(file) if File.symlink?(file) puts "\n Moving #{realpath}" end def exists_in_trusted?(file) trusted_path = File.join(@trusted_certs_dir, File.basename(file)) (File.symlink?(file) && File.readlink(file).start_with?(@trusted_certs_dir)) || (File.exist?(trusted_path) && FileUtils.identical?(file, trusted_path)) end def link_certificates update_permissions rehash_status = c_rehash unless rehash_status.zero? LoggingHelper.warning("Rehashing of trusted certificates present in `/etc/gitlab/trusted-certs` failed. If on a FIPS-enabled machine, ensure `c_rehash` binary is available in $PATH.") return end link_to_omnibus_ssl_directory log_directory_hash end # c_rehash ran so we now have valid hashed names # Skip all files that are not symlinks # If they are symlinks, make sure they are valid certificates def link_to_omnibus_ssl_directory Dir.glob(File.join(@trusted_certs_dir, "*")) do |trusted_cert| if File.symlink?(trusted_cert) && is_x509_certificate?(trusted_cert) hash_name = File.basename(trusted_cert) certificate_path = File.realpath(trusted_cert) symlink_path = File.join(@omnibus_certs_dir, hash_name) puts "\n Linking #{hash_name} from #{certificate_path}" FileUtils.ln_s certificate_path, symlink_path unless File.exist?(symlink_path) end end end def update_permissions files_directories = Dir.glob(File.join(@trusted_certs_dir, '*')) # Only operate on files file_list = files_directories.reject { |f| File.directory?(f) } FileUtils.chmod(0644, file_list) end def c_rehash cmd = "c_rehash #{@trusted_certs_dir}" result = do_shell_out_with_embedded_path(cmd) result.exitstatus end def log_directory_hash File.write(@directory_hash_file, trusted_certs_dir_hash) end def raise_msg(file) raise "ERROR: Not a certificate: #{File.realpath(file)}. Move it from #{File.realpath('..', file)} to a different location and reconfigure again." end end