##// END OF EJS Templates
Adds support for SCM/LDAP passwords encryption in the database (#7411)....
Jean-Philippe Lang -
r4830:a78d5659593d
parent child
Show More
@@ -0,0 +1,9
1 class ChangeRepositoriesPasswordLimit < ActiveRecord::Migration
2 def self.up
3 change_column :repositories, :password, :string, :limit => nil, :default => ''
4 end
5
6 def self.down
7 change_column :repositories, :password, :string, :limit => 60, :default => ''
8 end
9 end
@@ -0,0 +1,9
1 class ChangeAuthSourcesAccountPasswordLimit < ActiveRecord::Migration
2 def self.up
3 change_column :auth_sources, :account_password, :string, :limit => nil, :default => ''
4 end
5
6 def self.down
7 change_column :auth_sources, :account_password, :string, :limit => 60, :default => ''
8 end
9 end
@@ -0,0 +1,95
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 module Redmine
19 module Ciphering
20 def self.included(base)
21 base.extend ClassMethods
22 end
23
24 class << self
25 def encrypt_text(text)
26 if cipher_key.blank?
27 text
28 else
29 c = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
30 iv = c.random_iv
31 c.encrypt
32 c.key = cipher_key
33 c.iv = iv
34 e = c.update(text.to_s)
35 e << c.final
36 "aes-256-cbc:" + [e, iv].map {|v| Base64.encode64(v).strip}.join('--')
37 end
38 end
39
40 def decrypt_text(text)
41 if text && match = text.match(/\Aaes-256-cbc:(.+)\Z/)
42 text = match[1]
43 c = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
44 e, iv = text.split("--").map {|s| Base64.decode64(s)}
45 c.decrypt
46 c.key = cipher_key
47 c.iv = iv
48 d = c.update(e)
49 d << c.final
50 else
51 text
52 end
53 end
54
55 def cipher_key
56 key = Redmine::Configuration['database_cipher_key'].to_s
57 key.blank? ? nil : Digest::SHA256.hexdigest(key)
58 end
59 end
60
61 module ClassMethods
62 def encrypt_all(attribute)
63 transaction do
64 all.each do |object|
65 clear = object.send(attribute)
66 object.send "#{attribute}=", clear
67 raise(ActiveRecord::Rollback) unless object.save(false)
68 end
69 end ? true : false
70 end
71
72 def decrypt_all(attribute)
73 transaction do
74 all.each do |object|
75 clear = object.send(attribute)
76 object.write_attribute attribute, clear
77 raise(ActiveRecord::Rollback) unless object.save(false)
78 end
79 end
80 end ? true : false
81 end
82
83 private
84
85 # Returns the value of the given ciphered attribute
86 def read_ciphered_attribute(attribute)
87 Redmine::Ciphering.decrypt_text(read_attribute(attribute))
88 end
89
90 # Sets the value of the given ciphered attribute
91 def write_ciphered_attribute(attribute, value)
92 write_attribute(attribute, Redmine::Ciphering.encrypt_text(value))
93 end
94 end
95 end
@@ -0,0 +1,35
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18
19 namespace :db do
20 desc 'Encrypts SCM and LDAP passwords in the database.'
21 task :encrypt => :environment do
22 unless (Repository.encrypt_all(:password) &&
23 AuthSource.encrypt_all(:account_password))
24 raise "Some objects could not be saved after encryption, update was rollback'ed."
25 end
26 end
27
28 desc 'Decrypts SCM and LDAP passwords in the database.'
29 task :decrypt => :environment do
30 unless (Repository.decrypt_all(:password) &&
31 AuthSource.decrypt_all(:account_password))
32 raise "Some objects could not be saved after decryption, update was rollback'ed."
33 end
34 end
35 end
@@ -0,0 +1,84
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.expand_path('../../../../test_helper', __FILE__)
19
20 class Redmine::CipheringTest < ActiveSupport::TestCase
21
22 def test_password_should_be_encrypted
23 Redmine::Configuration.with 'database_cipher_key' => 'secret' do
24 r = Repository::Subversion.generate!(:password => 'foo')
25 assert_equal 'foo', r.password
26 assert r.read_attribute(:password).match(/\Aaes-256-cbc:.+\Z/)
27 end
28 end
29
30 def test_password_should_be_clear_with_blank_key
31 Redmine::Configuration.with 'database_cipher_key' => '' do
32 r = Repository::Subversion.generate!(:password => 'foo')
33 assert_equal 'foo', r.password
34 assert_equal 'foo', r.read_attribute(:password)
35 end
36 end
37
38 def test_password_should_be_clear_with_nil_key
39 Redmine::Configuration.with 'database_cipher_key' => nil do
40 r = Repository::Subversion.generate!(:password => 'foo')
41 assert_equal 'foo', r.password
42 assert_equal 'foo', r.read_attribute(:password)
43 end
44 end
45
46 def test_unciphered_password_should_be_readable
47 Redmine::Configuration.with 'database_cipher_key' => nil do
48 r = Repository::Subversion.generate!(:password => 'clear')
49 end
50
51 Redmine::Configuration.with 'database_cipher_key' => 'secret' do
52 r = Repository.first(:order => 'id DESC')
53 assert_equal 'clear', r.password
54 end
55 end
56
57 def test_encrypt_all
58 Repository.delete_all
59 Redmine::Configuration.with 'database_cipher_key' => nil do
60 Repository::Subversion.generate!(:password => 'foo')
61 Repository::Subversion.generate!(:password => 'bar')
62 end
63
64 Redmine::Configuration.with 'database_cipher_key' => 'secret' do
65 assert Repository.encrypt_all(:password)
66 r = Repository.first(:order => 'id DESC')
67 assert_equal 'bar', r.password
68 assert r.read_attribute(:password).match(/\Aaes-256-cbc:.+\Z/)
69 end
70 end
71
72 def test_decrypt_all
73 Repository.delete_all
74 Redmine::Configuration.with 'database_cipher_key' => 'secret' do
75 Repository::Subversion.generate!(:password => 'foo')
76 Repository::Subversion.generate!(:password => 'bar')
77
78 assert Repository.decrypt_all(:password)
79 r = Repository.first(:order => 'id DESC')
80 assert_equal 'bar', r.password
81 assert_equal 'bar', r.read_attribute(:password)
82 end
83 end
84 end
@@ -16,6 +16,8
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class AuthSource < ActiveRecord::Base
18 class AuthSource < ActiveRecord::Base
19 include Redmine::Ciphering
20
19 has_many :users
21 has_many :users
20
22
21 validates_presence_of :name
23 validates_presence_of :name
@@ -31,6 +33,14 class AuthSource < ActiveRecord::Base
31 def auth_method_name
33 def auth_method_name
32 "Abstract"
34 "Abstract"
33 end
35 end
36
37 def account_password
38 read_ciphered_attribute(:account_password)
39 end
40
41 def account_password=(arg)
42 write_ciphered_attribute(:account_password, arg)
43 end
34
44
35 def allow_password_changes?
45 def allow_password_changes?
36 self.class.allow_password_changes?
46 self.class.allow_password_changes?
@@ -20,8 +20,8 require 'iconv'
20
20
21 class AuthSourceLdap < AuthSource
21 class AuthSourceLdap < AuthSource
22 validates_presence_of :host, :port, :attr_login
22 validates_presence_of :host, :port, :attr_login
23 validates_length_of :name, :host, :account_password, :maximum => 60, :allow_nil => true
23 validates_length_of :name, :host, :maximum => 60, :allow_nil => true
24 validates_length_of :account, :base_dn, :maximum => 255, :allow_nil => true
24 validates_length_of :account, :account_password, :base_dn, :maximum => 255, :allow_nil => true
25 validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true
25 validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true
26 validates_numericality_of :port, :only_integer => true
26 validates_numericality_of :port, :only_integer => true
27
27
@@ -16,6 +16,8
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Repository < ActiveRecord::Base
18 class Repository < ActiveRecord::Base
19 include Redmine::Ciphering
20
19 belongs_to :project
21 belongs_to :project
20 has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
22 has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
21 has_many :changes, :through => :changesets
23 has_many :changes, :through => :changesets
@@ -24,6 +26,7 class Repository < ActiveRecord::Base
24 # has_many :changesets, :dependent => :destroy is too slow for big repositories
26 # has_many :changesets, :dependent => :destroy is too slow for big repositories
25 before_destroy :clear_changesets
27 before_destroy :clear_changesets
26
28
29 validates_length_of :password, :maximum => 255, :allow_nil => true
27 # Checks if the SCM is enabled when creating a repository
30 # Checks if the SCM is enabled when creating a repository
28 validate_on_create { |r| r.errors.add(:type, :invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
31 validate_on_create { |r| r.errors.add(:type, :invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
29
32
@@ -36,6 +39,14 class Repository < ActiveRecord::Base
36 def root_url=(arg)
39 def root_url=(arg)
37 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
40 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
38 end
41 end
42
43 def password
44 read_ciphered_attribute(:password)
45 end
46
47 def password=(arg)
48 write_ciphered_attribute(:password, arg)
49 end
39
50
40 def scm_adapter
51 def scm_adapter
41 self.class.scm_adapter_class
52 self.class.scm_adapter_class
@@ -124,6 +124,20 default:
124 scm_bazaar_command:
124 scm_bazaar_command:
125 scm_darcs_command:
125 scm_darcs_command:
126
126
127 # Key used to encrypt sensitive data in the database (SCM and LDAP passwords).
128 # If you don't want to enable data encryption, just leave it blank.
129 # WARNING: losing/changing this key will make encrypted data unreadable.
130 #
131 # If you want to encrypt existing passwords in your database:
132 # * set the cipher key here in your configuration file
133 # * encrypt data using 'rake db:encrypt RAILS_ENV=production'
134 #
135 # If you have encrypted data and want to change this key, you have to:
136 # * decrypt data using 'rake db:decrypt RAILS_ENV=production' first
137 # * change the cipher key here in your configuration file
138 # * encrypt data using 'rake db:encrypt RAILS_ENV=production'
139 database_cipher_key:
140
127 # specific configuration options for production environment
141 # specific configuration options for production environment
128 # that overrides the default ones
142 # that overrides the default ones
129 production:
143 production:
General Comments 0
You need to be logged in to leave comments. Login now