RSpec.describe Mdm::Cred, type: :model do it_should_behave_like 'Metasploit::Concern.run' context "Associations" do it { is_expected.to have_many(:task_creds).class_name('Mdm::TaskCred').dependent(:destroy) } it { is_expected.to have_many(:tasks).class_name('Mdm::Task').through(:task_creds) } it { is_expected.to belong_to(:service).class_name('Mdm::Service') } end context 'database' do context 'timestamps' do it { is_expected.to have_db_column(:created_at).of_type(:datetime) } it { is_expected.to have_db_column(:updated_at).of_type(:datetime) } end context 'columns' do it { is_expected.to have_db_column(:service_id).of_type(:integer).with_options(:null => false) } it { is_expected.to have_db_column(:user).of_type(:string) } it { is_expected.to have_db_column(:pass).of_type(:string) } it { is_expected.to have_db_column(:active).of_type(:boolean).with_options(:default => true) } it { is_expected.to have_db_column(:proof).of_type(:string) } it { is_expected.to have_db_column(:ptype).of_type(:string) } it { is_expected.to have_db_column(:source_id).of_type(:integer) } it { is_expected.to have_db_column(:source_type).of_type(:string) } end end context '#destroy' do it 'should successfully destroy the object and all dependent objects' do cred = FactoryGirl.create(:mdm_cred) task_cred = FactoryGirl.create(:mdm_task_cred, :cred => cred) expect { cred.destroy }.to_not raise_error expect { cred.reload }.to raise_error(ActiveRecord::RecordNotFound) expect { task_cred.reload }.to raise_error(ActiveRecord::RecordNotFound) end end context 'callbacks' do context 'after_create' do it 'should increment cred_count on the host' do host = FactoryGirl.create(:mdm_host) svc = FactoryGirl.create(:mdm_service, :host => host) expect { FactoryGirl.create(:mdm_cred, :service => svc) }.to change{ Mdm::Host.find(host.id).cred_count}.by(1) end end context 'after_destroy' do it 'should decrement cred_count on the host' do host = FactoryGirl.create(:mdm_host) svc = FactoryGirl.create(:mdm_service, :host => host) cred =FactoryGirl.create(:mdm_cred, :service => svc) expect { cred.destroy }.to change{ Mdm::Host.find(host.id).cred_count}.by(-1) end end end context 'constants' do it 'should define the key_id regex' do expect(described_class::KEY_ID_REGEX).to eq(/([0-9a-fA-F:]{47})/) end it 'should define ptypes to humanize' do expect(described_class::PTYPES).to eq( { 'read/write password' => 'password_rw', 'read-only password' => 'password_ro', 'SMB hash' => 'smb_hash', 'SSH private key' => 'ssh_key', 'SSH public key' => 'ssh_pubkey' } ) end end context 'methods' do let(:host) { FactoryGirl.create( :mdm_host, workspace: workspace ) } let(:other_service) { FactoryGirl.create( :mdm_service, host: host ) } let(:service) { FactoryGirl.create( :mdm_service, host: host ) } let(:ssh_key) { FactoryGirl.create( :mdm_cred, pass: '/path/to/keyfile', proof: "KEY=57:c3:11:5d:77:c5:63:90:33:2d:c5:c4:99:78:62:7a", ptype: 'ssh_key', service: service, user: 'msfadmin' ) } let(:ssh_pubkey) { FactoryGirl.create( :mdm_cred, pass: '/path/to/keyfile', proof: "KEY=57:c3:11:5d:77:c5:63:90:33:2d:c5:c4:99:78:62:7a", ptype: 'ssh_pubkey', service: service, user: 'msfadmin' ) } let(:workspace) { FactoryGirl.create(:mdm_workspace) } context '#ptype_human' do it "should return 'read/write password' for 'password_rw'" do cred = FactoryGirl.build(:mdm_cred, :user => 'msfadmin', :pass => 'msfadmin', :ptype => 'password_rw') expect(cred.ptype_human).to eq('read/write password') end it "should return 'read-only password' for 'password_ro'" do cred = FactoryGirl.build(:mdm_cred, :user => 'msfadmin', :pass => 'msfadmin', :ptype => 'password_ro') expect(cred.ptype_human).to eq('read-only password') end it "should return 'SMB Hash' for 'smb_hash'" do cred = FactoryGirl.build(:mdm_cred, :user => 'msfadmin', :pass => 'msfadmin', :ptype => 'smb_hash') expect(cred.ptype_human).to eq('SMB hash') end it "should return 'SSH private key' for 'ssh_key'" do cred = FactoryGirl.build(:mdm_cred, :user => 'msfadmin', :pass => 'msfadmin', :ptype => 'ssh_key') expect(cred.ptype_human).to eq('SSH private key') end it "should return 'SSH public key' for 'ssh_pubkey'" do cred = FactoryGirl.build(:mdm_cred, :user => 'msfadmin', :pass => 'msfadmin', :ptype => 'ssh_pubkey') expect(cred.ptype_human).to eq('SSH public key') end end context '#ssh_key_id' do it 'should return nil if not an ssh_key' do cred = FactoryGirl.build(:mdm_cred, :user => 'msfadmin', :pass => 'msfadmin', :ptype => 'password_rw') expect(cred.ssh_key_id).to eq(nil) end it 'should return nil if proof does not contain the key id' do cred = FactoryGirl.build(:mdm_cred, :user => 'msfadmin', :pass => '/path/to/keyfile', :ptype => 'ssh_key', :proof => "no key here") expect(cred.ssh_key_id).to eq(nil) end it 'should return the key id for an ssh_key' do cred = FactoryGirl.build(:mdm_cred, :user => 'msfadmin', :pass => '/path/to/keyfile', :ptype => 'ssh_key', :proof => "KEY=57:c3:11:5d:77:c5:63:90:33:2d:c5:c4:99:78:62:7a") expect(cred.ssh_key_id).to eq('57:c3:11:5d:77:c5:63:90:33:2d:c5:c4:99:78:62:7a') end end context '#ssh_key_matches?' do it 'should return true if the ssh_keys match' do other_ssh_key = FactoryGirl.create( :mdm_cred, pass: '/path/to/keyfile', proof: 'KEY=57:c3:11:5d:77:c5:63:90:33:2d:c5:c4:99:78:62:7a', ptype: 'ssh_key', service: other_service, user: 'msfadmin' ) expect(other_ssh_key.ssh_key_matches?(ssh_key)).to eq(true) end it 'should return false if passed something other than a cred' do expect(ssh_key.ssh_key_matches?(service)).to eq(false) end it 'should return false if the ptypes do not match' do different_ptype = FactoryGirl.create( :mdm_cred, pass: '/path/to/keyfile', proof: 'KEY=57:c3:11:5d:77:c5:63:90:33:2d:c5:c4:99:78:62:7a', ptype: 'ssh_pubkey', service: other_service, user: 'msfadmin' ) expect(different_ptype.ssh_key_matches?(ssh_key)).to eq(false) end it 'should return false if the key ids do not match' do different_proof = FactoryGirl.create( :mdm_cred, pass: '/path/to/keyfile', proof: 'KEY=66:d4:22:6e:88:d6:74:A1:44:3e:d6:d5:AA:89:73:8b', ptype: 'ssh_pubkey', service: other_service, user: 'msfadmin' ) expect(different_proof.ssh_key_matches?(ssh_key)).to eq(false) end it 'should behave the same for public keys as private keys' do pubkey2 = FactoryGirl.create(:mdm_cred, :service => service, :user => 'msfadmin', :pass => '/path/to/keyfile', :ptype => 'ssh_pubkey', :proof => "KEY=57:c3:11:5d:77:c5:63:90:33:2d:c5:c4:99:78:62:7a") pubkey3 = FactoryGirl.create(:mdm_cred, :service => service, :user => 'msfadmin', :pass => '/path/to/keyfile', :ptype => 'ssh_pubkey', :proof => "KEY=66:d4:22:6e:88:d6:74:A1:44:3e:d6:d5:AA:89:73:8b") expect(pubkey2.ssh_key_matches?(ssh_pubkey)).to eq(true) expect(pubkey2.ssh_key_matches?(pubkey3)).to eq(false) end it 'should always return false for non ssh key creds' do cred2 = FactoryGirl.create(:mdm_cred, :service => other_service, :ptype => 'password', :user => 'msfadmin', :pass => 'msfadmin' ) cred3 = FactoryGirl.create(:mdm_cred, :service => other_service, :ptype => 'password', :user => 'msfadmin', :pass => 'msfadmin' ) expect(cred2.ssh_key_matches?(cred3)).to eq(false) end end context '#ssh_keys' do # # lets # let(:other_ssh_key) { FactoryGirl.create( :mdm_cred, pass: '/path/to/keyfile', proof: 'KEY=57:c3:11:5d:77:c5:63:90:33:2d:c5:c4:99:78:62:7a', ptype: 'ssh_key', service: other_service, user: 'msfadmin' ) } # # Callbacks # before(:example) do ssh_key ssh_pubkey end it 'should return all ssh private keys with a matching id' do expect(other_ssh_key.ssh_keys).to include(ssh_key) end it 'should return all ssh public keys with a matching id' do expect(other_ssh_key.ssh_keys).to include(ssh_pubkey) end end context '#ssh_private_keys' do # # lets # let(:other_ssh_key) { FactoryGirl.create( :mdm_cred, pass: '/path/to/keyfile', proof: 'KEY=57:c3:11:5d:77:c5:63:90:33:2d:c5:c4:99:78:62:7a', ptype: 'ssh_key', service: other_service, user: 'msfadmin', ) } # # Callbacks # before(:example) do ssh_key ssh_pubkey end it 'should return ssh private keys with matching ids' do expect(other_ssh_key.ssh_private_keys).to include(ssh_key) end it 'should not return ssh public keys with matching ids' do expect(other_ssh_key.ssh_private_keys).not_to include(ssh_pubkey) end end context '#ssh_public_keys' do # # lets # let(:other_ssh_key) { FactoryGirl.create( :mdm_cred, pass: '/path/to/keyfile', proof: 'KEY=57:c3:11:5d:77:c5:63:90:33:2d:c5:c4:99:78:62:7a', ptype: 'ssh_key', service: other_service, user: 'msfadmin' ) } # # Callbacks # before(:example) do ssh_key ssh_pubkey end it 'should not return ssh private keys with matching ids' do expect(other_ssh_key.ssh_public_keys).not_to include(ssh_key) end it 'should return ssh public keys with matching ids' do expect(other_ssh_key.ssh_public_keys).to include(ssh_pubkey) end end end context 'factory' do it 'should be valid' do cred = FactoryGirl.build(:mdm_cred) expect(cred).to be_valid end end end