diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a5a7f3..bc80c55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ### master +## v6.0.0, 2018-07-09 +- Fix for pg_without_sudo; Wasn't adding -U to args +- New feature that will ALTER USER/Password with any change to pg_password. Random passwords will cause each cap setup to run the ALTER USER, but that's fine as a user should technically only be using setup initially. It's not that hard to obtain the new password if this happens. +- New redaction for logging of passwords & SSHKIT 1.17.0 in gemspec +- README updates + ## v5.0.1, 2018-06-05 - Quick fix for fetch(:pg_database) on extension adding diff --git a/README.md b/README.md index b1649a7..cdd2cfc 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Put the following in your application's `Gemfile`: group :development do gem 'capistrano', '~> 3.2.0' - gem 'capistrano-postgresql', '~> 5.0.0' + gem 'capistrano-postgresql', '~> 6.0.0' end Then: @@ -45,26 +45,28 @@ require 'capistrano/postgresql' You need to include ONLY ONE of the following in your config/deploy/*.rb files: ``` -set :pg_password, ENV['DATABASE_USER_PASSWORD'] -set :pg_ask_for_password, true -set :pg_generate_random_password, true +set :pg_password, ENV['DATABASE_USER_PASSWORD'] # Example is an ENV value, but you can use a string instead +set :pg_ask_for_password, true # Prompts user for password on execution of setup +set :pg_generate_random_password, true # Generates a random password on each setup ``` +##### Execution of `cap ENV setup` will run ALTER USER on pg_username if there is a different password. If you're using :pg_generate_random_password, you'll get a new random password on each run. + Example config: ``` -server 'growtrader.dev', user: 'growtrader', roles: %w{app db} +server 'yoursite.net', user: 'growtrader', roles: %w{app db} set :stage, :development set :branch, 'development' # ================== # Postgresql setup set :pg_without_sudo, false -set :pg_host, 'growtrader.dev' -set :pg_database, 'growtrader' -set :pg_username, 'growtrader' +set :pg_host, 'db.yoursite.net' +set :pg_database, 'pg_database_name_here' +set :pg_username, 'pg_username_here' #set :pg_generate_random_password, true #set :pg_ask_for_password, true -set :pg_password, ENV['GROWTRADER_PGPASS'] +set :pg_password, ENV['yoursite_PGPASS'] set :pg_extensions, ['citext','hstore'] set :pg_encoding, 'UTF-8' set :pg_pool, '100' @@ -72,15 +74,15 @@ set :pg_pool, '100' Finally, to setup the server(s), run: - $ bundle exec cap production setup + $ bundle exec cap development setup ### Requirements - * Be sure to remove `config/database.yml` from your application's version control. -* Your pg_hba.conf must include `local all all trust` +* Your pg_hba.conf must include `local all all trust`. We ssh into the servers to execute psql commands. * Make sure the `deploy_to` path exists and has the right privileges on your servers. The ~ symbol (i.e. `~/myapp`) is not supported. -* Within your app/config/deploy/{env}.rb files, you need to specify at least one :app and one :db server. +* Within your app/config/deploy/{env}.rb files, you need to specify at least one :app and one :db server (they can be on the same host; `roles: %w{web app db}`) * If you have multiple :db role hosts, it's necessary to specify `:primary => true` on the end of your primary :db server. +* gem >= 6.0.0 requires SSHKIT >= 1.17.0 as passwords are redacted from logging. ### How it works diff --git a/capistrano-postgresql.gemspec b/capistrano-postgresql.gemspec index 1a67c4b..fc11e50 100644 --- a/capistrano-postgresql.gemspec +++ b/capistrano-postgresql.gemspec @@ -24,5 +24,6 @@ Gem::Specification.new do |gem| gem.require_paths = ['lib'] gem.add_dependency 'capistrano', '>= 3.0' + gem.add_dependency 'sshkit', '>= 1.17.0' # 1.17.0 required for log/password redaction gem.add_development_dependency 'rake' end diff --git a/lib/capistrano/postgresql/helper_methods.rb b/lib/capistrano/postgresql/helper_methods.rb index d48d5a0..d7fa8cf 100644 --- a/lib/capistrano/postgresql/helper_methods.rb +++ b/lib/capistrano/postgresql/helper_methods.rb @@ -20,7 +20,7 @@ def remove_extensions end end - def generate_database_yml_io(password=fetch(:pg_password)) + def generate_database_yml_io StringIO.open do |s| s.puts "#{fetch(:pg_env)}:" { @@ -29,7 +29,7 @@ def generate_database_yml_io(password=fetch(:pg_password)) database: fetch(:pg_database), pool: fetch(:pg_pool), username: fetch(:pg_username), - password: password, + password: fetch(:pg_password), host: fetch(:pg_host), socket: fetch(:pg_socket), port: fetch(:pg_port), @@ -45,12 +45,7 @@ def pg_template(update=false,archetype_file=nil) raise('Regeneration of archetype database.yml need the original file to update from.') if archetype_file.nil? raise('Cannot update a custom postgresql.yml.erb file.') if File.exists?(config_file) # Skip custom postgresql.yml.erb if we're updating. It's not supported # Update yml file from settings - if fetch(:pg_generate_random_password) || !fetch(:pg_password) # We need to prevent updating the archetype file if we've done a random or "ask"ed password - current_password = archetype_file.split("\n").grep(/password/)[0].split('password:')[1].strip - generate_database_yml_io(current_password) - else - generate_database_yml_io - end + generate_database_yml_io else if File.exists?(config_file) # If there is a customized file in your rails app template directory, use it and convert any ERB StringIO.new ERB.new(File.read(config_file)).result(binding) diff --git a/lib/capistrano/postgresql/psql_helpers.rb b/lib/capistrano/postgresql/psql_helpers.rb index 070b638..7ec0854 100644 --- a/lib/capistrano/postgresql/psql_helpers.rb +++ b/lib/capistrano/postgresql/psql_helpers.rb @@ -3,16 +3,19 @@ module Postgresql module PsqlHelpers def psql(type, database, *args) - cmd = [ :psql, "-d #{database}", *args ] if fetch(:pg_without_sudo) - args.unshift("-U #{fetch(:pg_system_user)}") # Add the :pg_system_user to psql command since we aren't using sudo anymore + # Add the :pg_system_user to psql command since we aren't using sudo anymore + cmd = [ :psql, "-d #{database}", *args.unshift("-U #{fetch(:pg_system_user)}") ] else cmd = [:sudo, "-i -u #{fetch(:pg_system_user)}", *cmd] end + # Allow us to execute the different sshkit commands if type == 'test' - test *cmd.flatten + test *cmd + elsif type == 'capture' + capture *cmd else - execute *cmd.flatten + execute *cmd end end @@ -20,6 +23,12 @@ def database_user_exists? psql 'test', fetch(:pg_system_db),'-tAc', %Q{"SELECT 1 FROM pg_roles WHERE rolname='#{fetch(:pg_username)}';" | grep -q 1} end + def database_user_password_different? + current_password_md5 = psql 'capture', fetch(:pg_system_db),'-tAc', %Q{"select passwd from pg_shadow WHERE usename='#{fetch(:pg_username)}';"} + new_password_md5 = "md5#{Digest::MD5.hexdigest("#{fetch(:pg_password)}#{fetch(:pg_username)}")}" + current_password_md5 == new_password_md5 ? false : true + end + def database_exists? psql 'test', fetch(:pg_system_db), '-tAc', %Q{"SELECT 1 FROM pg_database WHERE datname='#{fetch(:pg_database)}';" | grep -q 1} end diff --git a/lib/capistrano/postgresql/version.rb b/lib/capistrano/postgresql/version.rb index cdf8e77..85353a5 100644 --- a/lib/capistrano/postgresql/version.rb +++ b/lib/capistrano/postgresql/version.rb @@ -1,5 +1,5 @@ module Capistrano module Postgresql - VERSION = '5.0.1' + VERSION = '6.0.0' end end diff --git a/lib/capistrano/tasks/postgresql.rake b/lib/capistrano/tasks/postgresql.rake index 6d24578..2bc7605 100644 --- a/lib/capistrano/tasks/postgresql.rake +++ b/lib/capistrano/tasks/postgresql.rake @@ -82,12 +82,16 @@ namespace :postgresql do end end - desc 'Create pg_username in database' + desc 'Create or update pg_username in database' task :create_database_user do on roles :db do unless database_user_exists? # If you use CREATE USER instead of CREATE ROLE the LOGIN right is granted automatically; otherwise you must specify it in the WITH clause of the CREATE statement. - psql 'execute', fetch(:pg_system_db), '-c', %Q{"CREATE USER \\"#{fetch(:pg_username)}\\" PASSWORD '#{fetch(:pg_password)}';"} + psql 'execute', fetch(:pg_system_db), '-c', %Q{"CREATE USER \\"#{fetch(:pg_username)}\\" PASSWORD}, redact("'#{fetch(:pg_password)}'"), %Q{;"} + end + if database_user_password_different? + # Ensure updating the password in your deploy/ENV.rb files updates the user, server side + psql 'execute', fetch(:pg_system_db), '-c', %Q{"ALTER USER \\"#{fetch(:pg_username)}\\" WITH PASSWORD}, redact("'#{fetch(:pg_password)}'"), %Q{;"} end end end @@ -140,7 +144,6 @@ namespace :postgresql do if release_roles(:app).empty? warn " WARNING: There are no servers in your app/config/deploy/#{fetch(:rails_env)}.rb with a :app role... Skipping Postgresql setup." else - invoke 'postgresql:remove_app_database_yml_files' # Deletes old yml files from all servers. Allows you to avoid having to manually delete the files on your app servers to get a new pool size for example. Don't touch the archetype file to avoid deleting generated passwords. if release_roles(:db).empty? # Test to be sure we have a :db role host warn " WARNING: There is no server in your app/config/deploy/#{fetch(:rails_env)}.rb with a :db role... Skipping Postgresql setup." elsif !fetch(:pg_password) && !fetch(:pg_generate_random_password) && !fetch(:pg_ask_for_password) @@ -148,6 +151,7 @@ namespace :postgresql do elsif fetch(:pg_generate_random_password) && fetch(:pg_ask_for_password) warn " WARNING: You cannot have both :pg_generate_random_password and :pg_ask_for_password enabled in app/config/deploy/#{fetch(:rails_env)}.rb." else + invoke 'postgresql:remove_app_database_yml_files' # Deletes old yml files from all servers. Allows you to avoid having to manually delete the files on your app servers to get a new pool size for example. Don't touch the archetype file to avoid deleting generated passwords. invoke 'postgresql:create_database_user' invoke 'postgresql:create_database' invoke 'postgresql:add_extensions'