Unfortunately, we have some stored RSA keys that ought to have been elliptic curve based for the significant performance advantages, in development, test, and production. This routine will first attempt to parse elliptic curves, and then RSA. If at some point we obsolete all stored RSA keys, this consideration can be eliminated.
115 lines
3.5 KiB
Ruby
115 lines
3.5 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "net/ssh"
|
|
require "openssl"
|
|
require "erubi"
|
|
require "tilt"
|
|
|
|
module Util
|
|
# A minimal, non-cached SSH implementation.
|
|
#
|
|
# It must log into an account that can escalate to root via "sudo,"
|
|
# which typically includes the "root" account reflexively. The
|
|
# ssh-agent is employed by default here, since personnel are thought
|
|
# to be involved with preparing new VmHosts.
|
|
def self.rootish_ssh(host, user, keys, cmd)
|
|
Net::SSH.start(host, user,
|
|
Sshable::COMMON_SSH_ARGS.merge(key_data: keys,
|
|
use_agent: Config.development?)) do |ssh|
|
|
ret = ssh.exec!(cmd)
|
|
fail "Ssh command failed: #{ret}" unless ret.exitstatus.zero?
|
|
ret
|
|
end
|
|
end
|
|
|
|
def self.parse_key(key_data)
|
|
OpenSSL::PKey::EC.new(key_data)
|
|
rescue OpenSSL::PKey::ECError, OpenSSL::PKey::DSAError
|
|
OpenSSL::PKey::RSA.new(key_data)
|
|
end
|
|
|
|
def self.create_root_certificate(common_name:, duration:)
|
|
create_certificate(
|
|
subject: "/C=US/O=Ubicloud/CN=#{common_name}",
|
|
extensions: ["basicConstraints=CA:TRUE", "keyUsage=cRLSign,keyCertSign", "subjectKeyIdentifier=hash"],
|
|
duration: duration
|
|
).map(&:to_pem)
|
|
end
|
|
|
|
def self.create_certificate(subject:, duration:, extensions: [], issuer_cert: nil, issuer_key: nil)
|
|
cert = OpenSSL::X509::Certificate.new
|
|
key = OpenSSL::PKey::EC.generate("prime256v1")
|
|
|
|
# If the issuer is nil, we will create a self-signed certificate.
|
|
if issuer_cert.nil?
|
|
issuer_cert = cert
|
|
issuer_key = key
|
|
end
|
|
|
|
# Set certificate details
|
|
cert.version = 2 # X.509v3
|
|
cert.serial = OpenSSL::BN.rand(128, 0)
|
|
cert.subject = OpenSSL::X509::Name.parse(subject)
|
|
cert.issuer = issuer_cert.subject
|
|
cert.not_before = Time.now
|
|
cert.not_after = Time.now + duration
|
|
cert.public_key = key
|
|
|
|
# Add extensions
|
|
ef = OpenSSL::X509::ExtensionFactory.new
|
|
ef.subject_certificate = cert
|
|
ef.issuer_certificate = issuer_cert
|
|
extensions.each do |extension|
|
|
cert.add_extension(ef.create_extension(extension))
|
|
end
|
|
|
|
# Sign
|
|
cert.sign(issuer_key, OpenSSL::Digest.new("SHA256"))
|
|
|
|
[cert, key]
|
|
end
|
|
|
|
def self.exception_to_hash(ex)
|
|
{exception: {message: ex.message, class: ex.class.to_s, backtrace: ex.backtrace, cause: ex.cause.inspect}}
|
|
end
|
|
|
|
def self.safe_write_to_file(filename, content)
|
|
FileUtils.mkdir_p(File.dirname(filename))
|
|
temp_filename = filename + ".tmp"
|
|
File.open("#{temp_filename}.lock", File::RDWR | File::CREAT) do |lock_file|
|
|
lock_file.flock(File::LOCK_EX)
|
|
File.write(temp_filename, content)
|
|
File.rename(temp_filename, filename)
|
|
end
|
|
end
|
|
|
|
def self.send_email(receiver, subject, greeting: nil, body: nil, button_title: nil, button_link: nil, cc: nil, attachments: [])
|
|
html = EmailRenderer.new.render "email/layout", locals: {subject: subject, greeting: greeting, body: body, button_title: button_title, button_link: button_link}
|
|
Mail.deliver do
|
|
from Config.mail_from
|
|
to receiver
|
|
subject subject
|
|
cc cc
|
|
|
|
attachments.each do |name, file|
|
|
add_file filename: name, content: file
|
|
end
|
|
|
|
text_part do
|
|
body "#{greeting}\n#{Array(body).join("\n")}\n#{button_link}"
|
|
end
|
|
|
|
html_part do
|
|
content_type "text/html; charset=UTF-8"
|
|
body html
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
class EmailRenderer
|
|
def render(template, locals: {})
|
|
Tilt::ErubiTemplate.new("views/#{template}.erb", escape: true, chain_appends: true, freeze: true, skip_compiled_encoding_detection: true).render(self, locals)
|
|
end
|
|
end
|