Created
March 25, 2021 20:46
-
-
Save fancyremarker/1bfbde4a01a34fe8dd00e88b4ee8261a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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? '[email protected]' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment