Skip to content

Instantly share code, notes, and snippets.

@fancyremarker
Created March 25, 2021 20:46
Show Gist options
  • Save fancyremarker/1bfbde4a01a34fe8dd00e88b4ee8261a to your computer and use it in GitHub Desktop.
Save fancyremarker/1bfbde4a01a34fe8dd00e88b4ee8261a to your computer and use it in GitHub Desktop.
# Our product relies on SSH for authentication and transport in various parts
# of the app. Most operations generate a certificate with particular
# restrictions to constrain what the user can do once they're authenticated to
# the SSH portal.
# The Core API would be making the permission decisions (i.e.
# what force command to use, whether to allocate port forwarding), and this
# module is responsible for providing a corresponding SSH Certificate.
# For example: DB Tunnel operations are allowed to port-forward, but they're
# not allowed to allocate a TTY. Their force command will instruct the SSH
# Portal to set up a port forward. Accessing logs does not allow port
# forwarding or allocating a TTY. Here, the forced command will instruct the
# SSH Portal to dump logs.
# We need to maximize our auditing capabilities. All the SSH
# Certificates we generate have a Certificate ID that we store for future
# reference (OpenSSH outputs the Certificate ID to logs when a user connects
# with it).
# This module accepts the following inputs, and generate a corresponding SSH
# Certificate:
# - A CA key (an RSA private key)
# - Username (aka "principal" in the docs linked above)
# - User's public key
# - Validity period
# - Certificate ID (aka "certificate_identity" in the ssh-keygen docs)
# - Forced command
# - Whether allocating PTY is allowed or not
# - Whether port forwarding is allowed
# In return, it provide the SSH Certificate as a string.
require 'open3'
class SSHCertificateGenerator
attr_reader :ca_private_key
def initialize(ca_private_key)
@ca_private_key = ca_private_key
end
def generate(options)
# Validate our mandatory fields are present
%i[username user_public_key].all? { |o| options.include? o }
# For optional fields, set default values if they aren't present
certificate_id = options.fetch(:certificate_id) { 'no-id' }
# Start building our command line options for ssh-keygen
cmd = [
'ssh-keygen',
'-s', ca_private_key,
'-I', certificate_id,
'-n', options[:username]
]
# Include these optionals restrictions only if present
cmd += ['-V', options[:validity_period]] if options.include? :validity_period
cmd += ['-O', options[:forced_command]] if options.include? :forced_command
cmd += ['-O', 'no-pty'] if options.include? :deny_pty
cmd += ['-O', 'no-port-forwarding'] if options.include? :deny_port_forwarding
cmd << options[:user_public_key]
msg, status = Open3.capture2e(cmd.join(' '))
fail msg unless status.success?
# This is the location where ssh-keygen outputs the SSH certificate
tmp_cert = options[:user_public_key].delete_suffix('.pub') + '-cert.pub'
File.read(tmp_cert)
end
end
require_relative 'security_review'
require 'openssl'
require 'tempfile'
PUBLIC_KEY = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDBhhgrB72tRuyulrOemCz6xuMa'\
'R+pWqloDP2pKOISSxINa+bfnw+LIH26mz9E68d3JdXAhrKNSnum7ZCTV2hDmVVo9'\
'43x6bqTkVL+a5HU1odzfRgpC6EzccrERBIzdKWJtCUU1GFtQYgnDWv4QLKlLIY4n'\
'JI9iiEG4e9lyC2eONC2j/V81v7ZWI7wLFFFOiv5XdAxhRoNUB1RkyUj73kjKwUW5'\
'0ZrefDxIrGOZ1mht8tJ9tYWG9+14n4VxCZ2tNe+L8D+36IwXuNvlipuwuX7G4VKb'\
'/othVeO3ojRGXQpPjy6IciP++Jl8enHu0YkNqcgnqi1mGAzQCaeLtbdvaNkbziX5'\
'81H0Gthrbivft5KcmBlDBKfdRoWVr5tgm0nYtN08grXyvIKL/GJe1h5JaZWgQpC/'\
'IUjjxPWRbH40DnAKeNtAoxZQjZ1XT8/i0gePBWrL7drFR9CaDqOUuZOYBsyivi03'\
'aWy0sNQ02Zxy0E2I/Z5zFuB4GuIzpIwWIUMo9tE= test'.freeze
ca = Tempfile.new(['ca', '.key'])
ca << OpenSSL::PKey::RSA.new(2048)
ca.rewind
generator = SSHCertificateGenerator.new(ca.path)
key = Tempfile.new(['key', '.pub'])
key << PUBLIC_KEY
key.rewind
options = {
username: 'test',
user_public_key: key.path
}
puts 'Generating a certificate'
cert = generator.generate(options)
fail 'invalid' unless cert.start_with? 'ssh-rsa-cert-v01@openssh.com'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment