Skip to content

Commit

Permalink
Add export command to gather data needed to migrate to EE (#641)
Browse files Browse the repository at this point in the history
* Add export command to gather data needed to migrate to EE

* Add test for `conjurctl export`

* Update changelog
  • Loading branch information
micahlee authored Jul 27, 2018
1 parent 95ffa50 commit f3339b4
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added
- Adds `conjurctl export` command to provide a migration data package to Conjur EE

## [1.0.1] - 2018-7-23
### Fixed
- Handling of absolute user ids in policies.
Expand Down
13 changes: 13 additions & 0 deletions bin/conjur-cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -272,4 +272,17 @@ def test_select
end
end

desc "Export the Conjur data for migration to Conjur Enteprise Edition"
command :export do |c|
c.desc "Output directory"
c.arg_name :out_dir
c.default_value Dir.pwd
c.flag [:o, :out_dir]

c.action do |global_options,options,args|
connect
exec "rake export[#{options[:out_dir]}]"
end
end

exit run(ARGV)
8 changes: 8 additions & 0 deletions cucumber/api/features/export.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Feature: Export Conjur Open Source data

The database and data key from Conjur can be exported to import
into a Conjur EE appliance

Scenario: Export using `conjurctl`
When I run conjurctl export
Then the export file exists
8 changes: 8 additions & 0 deletions cucumber/api/features/step_definitions/export_steps.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
When(/^I run conjurctl export$/) do
system("conjurctl export -o cuke_export/") || fail
end

Then(/^the export file exists$/) do
Dir['cuke_export/*.tar.xz.gpg'].any? || fail
File.exists?('cuke_export/key') || fail
end
5 changes: 5 additions & 0 deletions cucumber/api/features/support/hooks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Generates random unique names
#
require 'haikunator'
require 'fileutils'

Before do
@user_index = 0
Expand All @@ -22,6 +23,10 @@
Credentials.new(role: admin_role).save(raise_on_save_failure: true)
end

After do
FileUtils.remove_dir('cuke_export') if Dir.exists?('cuke_export')
end

Before("@logged-in") do
random_login = Haikunator.haikunate
@current_user = create_user(random_login, admin_user)
Expand Down
113 changes: 113 additions & 0 deletions lib/tasks/export.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# frozen_string_literal: true

require 'fileutils'
require 'time'
require 'pathname'

desc 'Export the Conjur data necessary to migrate to Enterprise Edition'
task :export, ['out_dir'] do |_t, args|
out_dir = Pathname.new args[:out_dir]

puts "Exporting to '#{out_dir}'..."
ExportTask.create_export_directory(out_dir)
export_key_file = ExportTask.ensure_export_key(out_dir)

dbdump = data_key_file = archive_file = nil
ExportTask.with_umask 077 do
dbdump = ExportTask.export_database(out_dir)
data_key_file = ExportTask.export_data_key(out_dir)
archive_file = ExportTask.create_export_archive(out_dir, dbdump, data_key_file)
end

ExportTask.encrypt_export_archive(export_key_file, archive_file)

puts
puts "Export placed in #{archive_file}.gpg"
puts "It's encrypted with key in #{export_key_file}."
puts "If you're going to store the export, make"
puts 'sure to store the key file separately.'

ensure
ExportTask.cleanup_export_files(archive_file, out_dir)
end

module ExportTask
class << self
def create_export_directory(out_dir)
# Make sure output directory exists and we can write to it
FileUtils.mkpath(out_dir)
FileUtils.chmod(0770, out_dir)
end

def ensure_export_key(out_dir)
export_key_file = out_dir.join('key')
if File.exist?(export_key_file)
puts "Using key from #{export_key_file}"
else
generate_key(export_key_file)
end
export_key_file
end

def export_database(out_dir)
# Export Conjur database
FileUtils.mkpath out_dir.join('backup')
dbdump = out_dir.join('backup/conjur.db')
call(%(pg_dump -Fc -f \"#{dbdump}\" #{ENV['DATABASE_URL']})) ||
raise('unable to get database backup')
dbdump
end

def export_data_key(out_dir)
# export CONJUR_DATA_KEY
FileUtils.mkpath out_dir.join('etc')
data_key_file = out_dir.join('etc/possum.key')
File.write(data_key_file, "CONJUR_DATA_KEY=#{ENV['CONJUR_DATA_KEY']}\n")
data_key_file
end

def create_export_archive(out_dir, dbdump, data_key_file)
# Timestamp to name export file
timestamp = Time.now.strftime('%Y-%m-%dT%H-%M-%SZ')

archive_file = out_dir.join("#{timestamp}.tar.xz")
call(%(tar Jcf "#{archive_file}" -C "#{out_dir}" ) +
%(--transform="s|^|/opt/conjur/|" ) +
%("#{dbdump.relative_path_from(out_dir)}" "#{data_key_file.relative_path_from(out_dir)}")) ||
raise('unable to make archive for backup')
archive_file
end

def encrypt_export_archive(export_key_file, archive_file)
call %(gpg -c --cipher-algo AES256 --batch --passphrase-file ) +
%("#{export_key_file}" --no-use-agent "#{archive_file}")
end

def cleanup_export_files(archive_file, out_dir)
call(%(rm -rf "#{archive_file}" "#{out_dir.join('backup')}" "#{out_dir.join('etc')}")) ||
warn('unable to remove temporary files')
end

def call(*args)
system(*args).tap do |result|
warn "command #{Array(args).join(' ')} failed" unless result
end
end

def generate_key(file)
with_umask 077 do
puts "Generating key file #{file}"
File.write(file, SecureRandom.base64(64))
end
end

def with_umask(umask)
saved_umask = File.umask umask
begin
yield
ensure
File.umask saved_umask
end
end
end
end

0 comments on commit f3339b4

Please sign in to comment.