Skip to content

Commit

Permalink
Merge branch 'master' into jgoizueta/ch66432/db-direct-allowed-ips-ma…
Browse files Browse the repository at this point in the history
…nagement

# Conflicts:
#	NEWS.md
  • Loading branch information
jgoizueta committed Apr 27, 2020
2 parents fdecb3f + 291d25c commit f102662
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 33 deletions.
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ gem 'test-unit'
gem 'rotp', '~> 3.3', '>= 3.3.1'
gem 'rqrcode', '~> 0.10.1'

# keys in PKCS#8 format require external command openssl
gem 'sys_cmd', '>= 1.1.3'

group :test do
gem 'simplecov', '0.13.0', require: false
gem 'simplecov-json'
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,8 @@ GEM
activerecord (>= 4.1, < 5.2)
state_machines-activemodel (>= 0.5.0)
statsd-client (0.0.7)
sys_cmd (1.1.3)
os (>= 0.9.6)
test-unit (3.1.7)
power_assert
thin (1.7.2)
Expand Down Expand Up @@ -549,6 +551,7 @@ DEPENDENCIES
sprockets (= 3.7.2)
state_machines-activerecord (~> 0.5.0)
statsd-client (= 0.0.7)
sys_cmd (>= 1.1.3)
test-unit
thin
typhoeus (= 1.3.1)
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Development

### Features
- GCP Firewall managemente for DB Direct IPs ([#15610](https://github.com/CartoDB/cartodb/pull/15610))
- Rake tasks to list DB Direct certificates ([#15625](https://github.com/CartoDB/cartodb/pull/15625))
- PKCS#8 keys support for DB-Direct certificates ([#15622](https://github.com/CartoDB/cartodb/pull/15622))

### Bug fixes / enhancements
- None yet
Expand Down
12 changes: 10 additions & 2 deletions app/controllers/carto/api/dbdirect_certificates_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,25 @@ def create
name: params[:name],
passphrase: params[:pass],
validity_days: validity_days,
server_ca: params[:server_ca]
server_ca: params[:server_ca],
pk8: params[:pk8],
)
result = {
id: cert.id,
name: cert.name, # must include name since we may have changed or generated it
client_key: data[:client_key],
client_key_pk8: data[:client_key_pk8],
client_crt: data[:client_crt],
server_ca: data[:server_ca]
}

respond_to do |format|
format.json do
render_jsonp(result, 201)
if result[:client_key_pk8].present?
render_jsonp({error: "binary DER in PKCS8 format not supported in JSON; use zip format"}, 422)
else
render_jsonp(result, 201)
end
end
format.zip do
zip_filename, zip_data = zip_certificates(result)
Expand Down Expand Up @@ -78,6 +84,7 @@ def zip_certificates(result)
certificate_id = result[:id]
certificate_name = result[:name]
client_key = result[:client_key]
client_key_pk8 = result[:client_key_pk8]
client_crt = result[:client_crt]
server_ca = result[:server_ca]

Expand All @@ -93,6 +100,7 @@ def zip_certificates(result)
zip_data = InMemZipper.zip(
'README.txt' => readme,
'client.key' => client_key,
'client.key.pk8' => client_key_pk8,
'client.crt' => client_crt,
'server_ca.pem' => server_ca
)
Expand Down
5 changes: 3 additions & 2 deletions app/models/carto/dbdirect_certificate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ class DbdirectCertificate < ActiveRecord::Base

before_destroy :revoke

def self.generate(user:, name:, passphrase: nil, validity_days: nil, server_ca: true)
def self.generate(user:, name:, passphrase: nil, validity_days: nil, server_ca: true, pk8: true)
validity_days ||= config['maximum_validity_days']
name = valid_name(user, name)

certificates, arn = certificate_manager.generate_certificate(
username: user.username,
passphrase: passphrase,
validity_days: validity_days,
server_ca: server_ca
server_ca: server_ca,
pk8: pk8
)

new_record = create(
Expand Down
52 changes: 28 additions & 24 deletions app/views/carto/api/dbdirect_certificates/README.txt.erb
Original file line number Diff line number Diff line change
@@ -1,38 +1,42 @@
Here's your certificate "<%= certificate_name %>" to use the CARTO Direct SQL BETA service.
Here's your certificate "<%= certificate_name %>" to use CARTO’s Direct SQL Connection beta version.

You can use it with any application that supports TSL/SSL connections to PostgreSQL
to directly access and modify your CARTO datasets.
You can use it with any application that supports SSL connections to PostgreSQL
with client identity verification to directly access and modify datasets stored in your CARTO account.

Contents:
Two versions of the private key are provided:
* RSA PEM format (client.key)
* DER PKCS #8 format (client.key.pk8)
Depending on the connection method used by your application you should use one or another.
For example, connections with the PostgreSQL ODBC driver should use the RSA PEM format and
connections with the PostgreSQL JDBC driver should use the DER PKCS #8 format.

* client.key This file contains your private key that is needed to encrypt the connections.
You must keep this file safely, as any one that gets access to this file may impersonate
you and access your data. On UNIX-like systems (Linux, Mac OS, ...) this file should be
protected with: `chmod 0600 client.key`.
* client.crt This is the certificate matching private key which will identify you
when you connect to your CARTO database. Keep this also safely.
* server_ca.pem This is optional and allows you to check the identity of CARTO's database server.
Contents:
* client.crt. Client certificate.
* client.key. Matching private key file in RSA PEM format. On UNIX-like systems (Linux, Mac OS, ...)
this file should be protected with: `chmod 0600 client.key`.
* client.key.pk8. Matching private key file in DER PKCS #8 format.
* server_ca.pem. This certificate allows you to check the identity of CARTO's database server.

You'll need to configure your application to use TSL with client.key and client.crt
You'll need to configure your application to use TLS with client.key and client.crt
(and optionally server_ca.pem) when connecting to your database.

Your database address (host server) is: <%= dbproxy_host %>
And the TCP port is: <%= dbproxy_port %>

You should use your CARTO account user name (<%= username %>) as your database user (role),
and an API Key as your password. You can generate API Keys from CARTO dashboard. The API Key you use
will determine which operations can be performed and which tables are accessible. We advise you
to generate specific keys and not use your master key, since the master key should be exposed as
little as possible, and it allows unrestricted access to your database.
and an API Key as your password. You can generate API Keys from your CARTO account dashboard
(‘API Keys’ section under your user profile on the top right of the screen).
The API key you use will determine which operations can be performed and which tables are accessible.
We advise you to generate specific keys and not to use your master API key.
We advise against exposing your Master API Key since it allows unrestricted access to your database.

Example: connect using psql:
psql "sslmode=verify-full sslrootcert=server_ca.pem \
sslcert=client.crt sslkey=client.key \
hostaddr=<%= dbproxy_host %> \
port=<%= dbproxy_port %> \
user=<%= username %>"

psql "sslmode=verify-ca sslrootcert=server_ca.pem \
sslcert=client.crt sslkey=client.key \
hostaddr=<%= dbproxy_host %> \
port=<%= dbproxy_port %> \
user=<%= username %>

This feature is in BETA. We'll provide more detailed information and guidelines very soon.
Please note that this feature is a beta version still undergoing testing before an official release.

Please contact CARTO support for further information.
Please contact CARTO support (support@carto.com) for further information or any questions you may have.
31 changes: 30 additions & 1 deletion lib/carto/dbdirect/certificate_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require 'aws-sdk-acmpca'
require 'json'
require 'securerandom'
require 'sys_cmd'

module Carto
module Dbdirect
Expand All @@ -13,10 +14,11 @@ def initialize(config)

attr_reader :config

def generate_certificate(username:, passphrase:, validity_days:, server_ca:)
def generate_certificate(username:, passphrase:, validity_days:, server_ca:, pk8:)
certificates = nil
arn = nil
key = openssl_generate_key(passphrase)
key_pk8 = openssl_converty_key_to_pkcs8(key, passphrase) if pk8
csr = openssl_generate_csr(username, key, passphrase)
with_aws_credentials do
arn = aws_issue_certificate(csr, validity_days)
Expand All @@ -26,6 +28,7 @@ def generate_certificate(username:, passphrase:, validity_days:, server_ca:)
client_crt: certificate
}
certificates[:server_ca] = aws_get_ca_certificate_chain if server_ca
certificates[:client_key_pk8] = key_pk8 if pk8
end
[certificates, arn]
end
Expand Down Expand Up @@ -55,6 +58,20 @@ def openssl_generate_key(passphrase)
end
end

def openssl_converty_key_to_pkcs8(key, passphrase)
cmd = SysCmd.command 'openssl pkcs8' do
option '-topk8'
option '-inform', 'PEM'
option '-passin', "pass:#{passphrase}" if passphrase.present?
input key
option '-outform', 'DER'
option '-passout', "pass:#{passphrase}" if passphrase.present?
option '-nocrypt' unless passphrase.present?
end
run cmd
cmd.output.force_encoding(Encoding::ASCII_8BIT)
end

def openssl_generate_csr(username, key, passphrase)
subj = OpenSSL::X509::Name.parse("/C=US/ST=New York/L=New York/O=CARTO/OU=Customer/CN=#{username}")
if passphrase.present?
Expand Down Expand Up @@ -150,6 +167,18 @@ def with_env(vars)
ENV[key] = old[key]
end
end

def run(cmd, error_message: 'Error')
result = cmd.run direct: true, error_output: :separate
if cmd.error
raise cmd.error
elsif cmd.status_value != 0
msg = error_message
msg += ": " + cmd.error_output if cmd.error_output.present?
raise msg
end
result
end
end
end
end
43 changes: 40 additions & 3 deletions lib/tasks/dbdirect.rake
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@ namespace :carto do
key_filename = File.join(output_directory, "#{base_filename}.key")
crt_filename = File.join(output_directory, "#{base_filename}.crt")
ca_filename = File.join(output_directory, "server_ca.crt")
pk8_filename = File.join(output_directory, "#{base_filename}.key.pk8")
puts "Certificate #{name} generated for #{username}"
puts " id: #{id}"
puts " arn: #{arn}"
[[:client_key, key_filename], [:client_crt, crt_filename], [:server_ca, ca_filename]].each do |datum, filename|
File.open(filename, 'w') do |file|
files = [
[:client_key, key_filename],
[:client_key_pk8, pk8_filename, 'b'],
[:client_crt, crt_filename],
[:server_ca, ca_filename]
]
files.each do |datum, filename, binary|
File.open(filename, "w#{binary}") do |file|
file.write data[datum]
end
puts "#{datum} written to #{filename}"
Expand All @@ -33,12 +40,42 @@ namespace :carto do
name: args.name,
passphrase: args.password,
validity_days: args.validity.blank? ? 365 : args.validity.to_i,
server_ca: true
server_ca: true,
pk8: true
)

save_certs user.username, cert.name, cert.id, cert.arn, data, '.'
end

desc "Show DB direct user certificates"
task :user_certificates, [:username] => :environment do |_t, args|
user = Carto::User.find_by_username(args.username)
raise "User #{args.username} not found" unless user

user.dbdirect_certificates.each do |certificate|
puts "#{certificate.name}:"
puts " id: #{certificate.id}"
puts " expires: #{certificate.expiration}"
puts " arn: #{certificate.arn}"
end
end

desc "Show DB direct organization certificates"
task :organization_certificates, [:org] => :environment do |_t, args|
organization = Carto::Organization.find_by_id(args.org) || Carto::Organization.find_by_name(args.org)
raise "Couldn't find organization #{args.org.inspect}" unless organization.present?

organization.users.each do |user|
user.dbdirect_certificates.each do |certificate|
puts "#{certificate.name}:"
puts " username: #{user.username}"
puts " id: #{certificate.id}"
puts " expires: #{certificate.expiration}"
puts " arn: #{certificate.arn}"
end
end
end

desc "Revoke DB direct certificate"
task :revoke_certificate, [:id] => :environment do |_t, args|
cert = Carto::DbdirectCertificate.find(args.id)
Expand Down
Loading

0 comments on commit f102662

Please sign in to comment.