diff --git a/Netdisco/META.yml b/Netdisco/META.yml index 9de36497..03af017f 100644 --- a/Netdisco/META.yml +++ b/Netdisco/META.yml @@ -34,6 +34,7 @@ requires: Dancer: 1.3112 Dancer::Plugin::Auth::Extensible: 0.3 Dancer::Plugin::DBIC: 0.1803 + Dancer::Plugin::Passphrase: 2 File::ShareDir: 1.03 Guard: 1.022 HTML::Parser: 3.7 diff --git a/Netdisco/Makefile.PL b/Netdisco/Makefile.PL index 3ab69e7c..1232dcad 100644 --- a/Netdisco/Makefile.PL +++ b/Netdisco/Makefile.PL @@ -18,6 +18,7 @@ requires 'Daemon::Control' => 0.001000; requires 'Dancer' => 1.3112; requires 'Dancer::Plugin::DBIC' => 0.1803; requires 'Dancer::Plugin::Auth::Extensible' => 0.30; +requires 'Dancer::Plugin::Passphrase' => 2.00; requires 'File::ShareDir' => 1.03; requires 'Guard' => 1.022; requires 'HTML::Parser' => 3.70; diff --git a/Netdisco/bin/netdisco-deploy b/Netdisco/bin/netdisco-deploy index 96e4105b..ced416e7 100755 --- a/Netdisco/bin/netdisco-deploy +++ b/Netdisco/bin/netdisco-deploy @@ -99,9 +99,9 @@ $bool = $term->ask_yn( ); deploy_db() if $bool; +say ''; my $users = schema('netdisco')->resultset('User'); if ($users->count == 0) { - say ''; $bool = $term->ask_yn( prompt => 'Would you like a default web user with Admin rights (discover, etc)?', default => 'n', @@ -136,6 +136,10 @@ if ($users->count == 0) { } } } +elsif (!setting('safe_password_store')) { + say '*** WARNING: Weak password hashes are being stored in the database! ***'; + say '*** WARNING: Please add "safe_password_store: true" to your ~/environments/deployment.yml file. ***'; +} say ''; $bool = $term->ask_yn( diff --git a/Netdisco/lib/App/Netdisco.pm b/Netdisco/lib/App/Netdisco.pm index 8796a34a..74eef98c 100644 --- a/Netdisco/lib/App/Netdisco.pm +++ b/Netdisco/lib/App/Netdisco.pm @@ -210,7 +210,8 @@ C, C, C and C). In the same file uncomment and edit the C setting to be appropriate for your local site. If this is a fresh install, uncomment and set -the C value to true (temporarily disables user authentication). +the C value to true (temporarily disables user authentication). Have +a quick read of the other settings to make sure you're happy, then move on. =head1 Bootstrap @@ -243,7 +244,7 @@ daemon at the same time. Similarly, if you use the device discovery with Netdisco 2, disable your system's cron jobs for the Netdisco 1.x poller. At this point you can revisit the C<~/environments/deployment.yml> file to -uncomment more configuration. Check out the community string settings, and +uncomment more configuration. Enable the C string settings, and C which enables the automatic periodic device discovery. See L for further details. diff --git a/Netdisco/lib/App/Netdisco/Manual/Configuration.pod b/Netdisco/lib/App/Netdisco/Manual/Configuration.pod index 5c3d82af..b12a2354 100644 --- a/Netdisco/lib/App/Netdisco/Manual/Configuration.pod +++ b/Netdisco/lib/App/Netdisco/Manual/Configuration.pod @@ -223,6 +223,13 @@ Value: List of Modules. Default: Empty List. List of additional L names to load. See also the C setting. +=head3 C + +Value: Boolean. Default: true. + +Set to "C" if you MUST maintain backwards compatibility with the Netdisco +1.x web interface. Strongly recommended that you leave this set to "C". + =head2 Netdisco Core =head3 C diff --git a/Netdisco/lib/App/Netdisco/Web.pm b/Netdisco/lib/App/Netdisco/Web.pm index e52f7680..f07b988d 100644 --- a/Netdisco/lib/App/Netdisco/Web.pm +++ b/Netdisco/lib/App/Netdisco/Web.pm @@ -90,61 +90,6 @@ hook 'after' => sub { } }; -get qr{^/(?:login(?:/denied)?)?} => sub { - template 'index'; -}; - -# Override default login_handler so that we can log access in the -# database -post '/login' => sub { - my ($success, $realm) = authenticate_user( - params->{username}, params->{password} - ); - if ($success) { - session logged_in_user => params->{username}; - session logged_in_user_realm => $realm; - - schema('netdisco')->resultset('UserLog')->create({ - username => session('logged_in_user'), - userip => request->remote_address, - event => "Login", - details => params->{return_url}, - }); - - redirect params->{return_url} || uri_for('/'); - } else { - - schema('netdisco')->resultset('UserLog')->create({ - username => params->{username}, - userip => request->remote_address, - event => "Login Failure", - details => params->{return_url}, - }); - - vars->{login_failed}++; - forward uri_for('/login'), { login_failed => 1 }, { method => 'GET' }; - } -}; - -# Since we override the default login_handler, logout has to be handled as -# well -any ['get','post'] => '/logout' => sub { - - schema('netdisco')->resultset('UserLog')->create({ - username => session('logged_in_user'), - userip => request->remote_address, - event => "Logout", - details => '', - }); - - session->destroy; - if (params->{return_url}) { - redirect params->{return_url}; - } else { - return "OK, logged out successfully."; - } -}; - any qr{.*} => sub { var('notfound' => true); status 'not_found'; diff --git a/Netdisco/lib/App/Netdisco/Web/Auth/Provider/DBIC.pm b/Netdisco/lib/App/Netdisco/Web/Auth/Provider/DBIC.pm index 2f51111e..e124ae93 100644 --- a/Netdisco/lib/App/Netdisco/Web/Auth/Provider/DBIC.pm +++ b/Netdisco/lib/App/Netdisco/Web/Auth/Provider/DBIC.pm @@ -10,7 +10,7 @@ use base 'Dancer::Plugin::Auth::Extensible::Provider::Base'; use Dancer ':syntax'; use Dancer::Plugin::DBIC; - +use Dancer::Plugin::Passphrase; use Digest::MD5; sub authenticate_user { @@ -77,8 +77,25 @@ sub match_with_local_pass { return unless $password and $user->$password_column; - my $sum = Digest::MD5::md5_hex($password); - return ($sum eq $user->$password_column ? 1 : 0); + if ($user->$password_column !~ m/^{[A-Z]+}/) { + debug 'authN: using legacy MD5'; + my $sum = Digest::MD5::md5_hex($password); + + if ($sum eq $user->$password_column) { + if (setting('safe_password_store')) { + # upgrade password if successful, and permitted + $user->update({password => passphrase($password)->generate}); + } + return 1; + } + else { + return 0; + } + } + else { + debug 'authN: using Passphrase'; + return passphrase($password)->matches($user->$password_column); + } } sub match_with_ldap { diff --git a/Netdisco/lib/App/Netdisco/Web/AuthN.pm b/Netdisco/lib/App/Netdisco/Web/AuthN.pm index bb8afd7e..40578472 100644 --- a/Netdisco/lib/App/Netdisco/Web/AuthN.pm +++ b/Netdisco/lib/App/Netdisco/Web/AuthN.pm @@ -27,4 +27,59 @@ hook 'before' => sub { } }; +get qr{^/(?:login(?:/denied)?)?} => sub { + template 'index'; +}; + +# override default login_handler so we can log access in the database +post '/login' => sub { + my ($success, $realm) = authenticate_user( + params->{username}, params->{password} + ); + + if ($success) { + session logged_in_user => params->{username}; + session logged_in_user_realm => $realm; + + schema('netdisco')->resultset('UserLog')->create({ + username => session('logged_in_user'), + userip => request->remote_address, + event => "Login", + details => params->{return_url}, + }); + + redirect params->{return_url} || uri_for('/'); + } + else { + schema('netdisco')->resultset('UserLog')->create({ + username => params->{username}, + userip => request->remote_address, + event => "Login Failure", + details => params->{return_url}, + }); + + vars->{login_failed}++; + forward uri_for('/login'), { login_failed => 1 }, { method => 'GET' }; + } +}; + +# we override the default login_handler, so logout has to be handled as well +any ['get','post'] => '/logout' => sub { + + schema('netdisco')->resultset('UserLog')->create({ + username => session('logged_in_user'), + userip => request->remote_address, + event => "Logout", + details => '', + }); + + session->destroy; + if (params->{return_url}) { + redirect params->{return_url}; + } + else { + return "OK, logged out successfully."; + } +}; + true; diff --git a/Netdisco/lib/App/Netdisco/Web/Plugin/AdminTask/Users.pm b/Netdisco/lib/App/Netdisco/Web/Plugin/AdminTask/Users.pm index 2a34c579..ec2f79e3 100644 --- a/Netdisco/lib/App/Netdisco/Web/Plugin/AdminTask/Users.pm +++ b/Netdisco/lib/App/Netdisco/Web/Plugin/AdminTask/Users.pm @@ -4,6 +4,7 @@ use Dancer ':syntax'; use Dancer::Plugin::Ajax; use Dancer::Plugin::DBIC; use Dancer::Plugin::Auth::Extensible; +use Dancer::Plugin::Passphrase; use App::Netdisco::Web::Plugin; use Digest::MD5 (); @@ -21,6 +22,16 @@ sub _sanity_ok { return 1; } +sub _make_password { + my $pass = (shift || passphrase->generate_random); + if (setting('safe_password_store')) { + return passphrase($pass)->generate; + } + else { + return Digest::MD5::md5_hex($pass), + } +} + ajax '/ajax/control/admin/users/add' => require_role admin => sub { send_error('Bad Request', 400) unless _sanity_ok(); @@ -28,7 +39,7 @@ ajax '/ajax/control/admin/users/add' => require_role admin => sub { my $user = schema('netdisco')->resultset('User') ->create({ username => param('username'), - password => Digest::MD5::md5_hex(param('password')), + password => _make_password(param('password')), fullname => param('fullname'), ldap => (param('ldap') ? \'true' : \'false'), port_control => (param('port_control') ? \'true' : \'false'), @@ -56,7 +67,7 @@ ajax '/ajax/control/admin/users/update' => require_role admin => sub { $user->update({ ((param('password') ne '********') - ? (password => Digest::MD5::md5_hex(param('password'))) + ? (password => _make_password(param('password'))) : ()), fullname => param('fullname'), ldap => (param('ldap') ? \'true' : \'false'), diff --git a/Netdisco/share/config.yml b/Netdisco/share/config.yml index 731e24b3..cb0a2b92 100644 --- a/Netdisco/share/config.yml +++ b/Netdisco/share/config.yml @@ -72,6 +72,7 @@ web_plugins: - Device::Neighbors - Device::Addresses extra_web_plugins: [] +safe_password_store: true # ------------- # NETDISCO CORE diff --git a/Netdisco/share/environments/deployment.yml b/Netdisco/share/environments/deployment.yml index 6bd83fe6..d2ee9277 100644 --- a/Netdisco/share/environments/deployment.yml +++ b/Netdisco/share/environments/deployment.yml @@ -18,6 +18,11 @@ database: # RECOMMENDED SETTINGS # -------------------- +# set to "false" if you MUST maintain backwards compatibility +# with Netdisco 1.x web frontend. +# ``````````````````````````````````````````````````````````` +safe_password_store: true + # will be stripped from fqdn when displayed in the web UI # also, do not forget the leading dot. # ``````````````````````````````````````````````````````` @@ -48,8 +53,8 @@ database: # expiry: # when: '20 23 * * *' -# increase the performance of parallel DNS resolution for node names -# (the default is max_outstanding: 10) +# increase the performance of parallel DNS resolution for node +# names (the default is max_outstanding: 10) +# ```````````````````````````````````````````````````````````` #dns: # max_outstanding: 100 -