Skip to content

Commit

Permalink
Merge pull request #19007 from NickLaMuro/ansible_runner_network_cred…
Browse files Browse the repository at this point in the history
…ential

[ansible_runner] Add NetworkCredential for Ansible::Runner lib
  • Loading branch information
carbonin authored Jul 19, 2019
2 parents a8a06f8 + bcdd800 commit 837761c
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class ManageIQ::Providers::EmbeddedAnsible::AutomationManager::NetworkCredential
# rubocop:disable Layout/AlignHash
#
# looks better to align the nested keys to the same distance, instead of
# scope just for the hash in question (which is what rubocop does.
# scope just for the hash in question (which is what rubocop does).
EXTRA_ATTRIBUTES = {
:authorize => {
:type => :boolean,
Expand Down Expand Up @@ -62,9 +62,9 @@ def self.display_name(number = 1)
def self.params_to_attributes(params)
attrs = params.dup

attrs[:auth_key] = attrs.delete(:ssh_key_data)
attrs[:auth_key_password] = attrs.delete(:ssh_key_unlock)
attrs[:become_password] = attrs.delete(:authorize_password)
attrs[:auth_key] = attrs.delete(:ssh_key_data)
attrs[:auth_key_password] = attrs.delete(:ssh_key_unlock)
attrs[:become_password] = attrs.delete(:authorize_password)

if attrs[:authorize]
attrs[:options] = { :authorize => attrs.delete(:authorize) }
Expand Down
4 changes: 4 additions & 0 deletions lib/ansible/runner/credential.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ def write_config_files

private

def initialize_password_data
File.exist?(password_file) ? YAML.load_file(password_file) : {}
end

def password_file
File.join(env_dir, "passwords")
end
Expand Down
12 changes: 7 additions & 5 deletions lib/ansible/runner/credential/machine_credential.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ def become_args
}
end

SSH_KEY = "^SSH [pP]assword:".freeze
BECOME_KEY = "^BECOME [pP]assword:".freeze
SSH_UNLOCK_KEY = "^Enter passphrase for [a-zA-Z0-9\-\/]+\/ssh_key_data:".freeze
def write_password_file
password_hash = {
"^SSH [pP]assword:" => auth.password,
"^BECOME [pP]assword:" => auth.become_password,
"^Enter passphrase for [a-zA-Z0-9\-\/]+\/ssh_key_data:" => auth.ssh_key_unlock
}.delete_blanks
password_hash = initialize_password_data
password_hash[SSH_KEY] = auth.password if auth.password
password_hash[BECOME_KEY] = auth.become_password if auth.become_password
password_hash[SSH_UNLOCK_KEY] = auth.ssh_key_unlock if auth.ssh_key_unlock

File.write(password_file, password_hash.to_yaml) if password_hash.present?
end
Expand Down
48 changes: 48 additions & 0 deletions lib/ansible/runner/credential/network_credential.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module Ansible
class Runner
class NetworkCredential < Credential
def self.auth_type
"ManageIQ::Providers::EmbeddedAnsible::AutomationManager::NetworkCredential"
end

# Modeled off of awx codebase:
#
# https://github.com/ansible/awx/blob/1242ee2b/awx/main/tasks.py#L1432-L1443
#
def env_vars
env = {
"ANSIBLE_NET_USERNAME" => auth.userid || "",
"ANSIBLE_NET_PASSWORD" => auth.password || "",
"ANSIBLE_NET_AUTHORIZE" => auth.authorize ? "1" : "0"
}

env["ANSIBLE_NET_AUTH_PASS"] = auth.become_password || "" if auth.authorize
env["ANSIBLE_NET_SSH_KEYFILE"] = network_ssh_key_file if auth.auth_key
env
end

def write_config_files
write_password_file
write_network_ssh_key_file if auth.auth_key
end

private

SSH_UNLOCK_KEY = "^Enter passphrase for [a-zA-Z0-9\-\/]+\/ssh_key_data:".freeze
def write_password_file
password_data = initialize_password_data
password_data[SSH_UNLOCK_KEY] ||= auth.ssh_key_unlock || ""
File.write(password_file, password_data.to_yaml)
end

def write_network_ssh_key_file
File.write(network_ssh_key_file, auth.auth_key)
File.chmod(0o0400, network_ssh_key_file)
end

def network_ssh_key_file
File.join(env_dir, "network_ssh_key")
end
end
end
end
34 changes: 34 additions & 0 deletions spec/lib/ansible/runner/credential/machine_credential_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,40 @@ def password_hash

expect(password_hash["^SSH [pP]assword:"]).to eq(password)
end

context "with an existing password_file" do
let(:ssh_unlock_key) { "^Enter passphrase for [a-zA-Z0-9\-\/]+\/ssh_key_data:" }
def existing_env_password_file(data)
cred # initialize the dir
File.write(password_file, data.to_yaml)
end

it "clobbers existing ssh key unlock keys" do
existing_data = { ssh_unlock_key => "hunter2" }
expected_data = {
"^SSH [pP]assword:" => "secret",
"^BECOME [pP]assword:" => "othersecret",
ssh_unlock_key => "keypass"
}
existing_env_password_file(existing_data)
cred.write_config_files

expect(password_hash).to eq(expected_data)
end

it "appends data if not setting ssh_unlock_key" do
auth.update!(:auth_key_password => nil)
existing_data = { ssh_unlock_key => "hunter2" }
added_data = {
"^SSH [pP]assword:" => "secret",
"^BECOME [pP]assword:" => "othersecret"
}
existing_env_password_file(existing_data)
cred.write_config_files

expect(password_hash).to eq(existing_data.merge(added_data))
end
end
end
end
end
186 changes: 186 additions & 0 deletions spec/lib/ansible/runner/credential/network_credential_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
require 'ansible/runner'
require 'ansible/runner/credential'

RSpec.describe Ansible::Runner::NetworkCredential do
it ".auth_type is the correct Authentication sub-class" do
expect(described_class.auth_type).to eq("ManageIQ::Providers::EmbeddedAnsible::AutomationManager::NetworkCredential")
end

context "with a credential object" do
around do |example|
Dir.mktmpdir("ansible-runner-credential-test") do |dir|
@base_dir = dir
example.run
end
end

let(:auth) { FactoryBot.create(:embedded_ansible_network_credential, auth_attributes) }
let(:cred) { described_class.new(auth.id, @base_dir) }
let(:key_file) { File.join(@base_dir, "env", "network_ssh_key") }
let(:auth_attributes) do
{
:userid => "manageiq-network",
:password => "network_secret"
}
end

describe "#command_line" do
it "returns an empty hash" do
expect(cred.command_line).to eq({})
end
end

# Modeled off of awx codebase:
#
# https://github.com/ansible/awx/blob/1242ee2b/awx/main/tasks.py#L1432-L1443
#
describe "#env_vars" do
it "sets ANSIBLE_NET_USERNAME, ANSIBLE_NET_PASSWORD, and ANSIBLE_NET_AUTHORIZE" do
expected = {
"ANSIBLE_NET_USERNAME" => "manageiq-network",
"ANSIBLE_NET_PASSWORD" => "network_secret",
"ANSIBLE_NET_AUTHORIZE" => "0",
}
expect(cred.env_vars).to eq(expected)
end

context "with an auth_key" do
let(:auth_attributes) do
{
:userid => "",
:password => "",
:auth_key => "key_data"
}
end

it "sets ANSIBLE_NET_SSH_KEYFILE to the network_ssh_key_file location" do
expected = {
"ANSIBLE_NET_USERNAME" => "",
"ANSIBLE_NET_PASSWORD" => "",
"ANSIBLE_NET_AUTHORIZE" => "0",
"ANSIBLE_NET_SSH_KEYFILE" => key_file
}
expect(cred.env_vars).to eq(expected)
end
end

context "with authorize set" do
let(:auth_attributes) do
{
:userid => "user",
:password => "pass",
:options => { :authorize => true }
}
end

it "sets ANSIBLE_NET_AUTHORIZE to '1'" do
expected = {
"ANSIBLE_NET_USERNAME" => "user",
"ANSIBLE_NET_PASSWORD" => "pass",
"ANSIBLE_NET_AUTHORIZE" => "1",
"ANSIBLE_NET_AUTH_PASS" => ""
}
expect(cred.env_vars).to eq(expected)
end

it "defines ANSIBLE_NET_AUTH_PASS if it is present" do
auth.update!(:become_password => "auth_pass")
expected = {
"ANSIBLE_NET_USERNAME" => "user",
"ANSIBLE_NET_PASSWORD" => "pass",
"ANSIBLE_NET_AUTHORIZE" => "1",
"ANSIBLE_NET_AUTH_PASS" => "auth_pass"
}
expect(cred.env_vars).to eq(expected)
end
end
end

describe "#extra_vars" do
it "returns an empty hash" do
expect(cred.extra_vars).to eq({})
end
end

describe "#write_config_files" do
let(:password_file) { File.join(@base_dir, "env", "passwords") }

def password_hash
YAML.load_file(password_file)
end

context "with an auth_key" do
let(:auth_attributes) { { :auth_key => "key_data" } }

it "writes the network_ssh_key_file" do
cred.write_config_files
expect(File.read(key_file)).to eq("key_data")
expect(File.stat(key_file).mode).to eq(0o100400)
end
end

context "without an auth_key" do
it "writes the network_ssh_key_file" do
cred.write_config_files
expect(File.exist?(key_file)).to be_falsey
end
end

context "with authorize set" do
let(:ssh_unlock_key) { "^Enter passphrase for [a-zA-Z0-9\-\/]+\/ssh_key_data:" }
let(:auth_attributes) do
{
:userid => "user",
:password => "pass",
:auth_key_password => "key_pass",
:options => { :authorize => true }
}
end

it "writes the password file" do
cred.write_config_files

expect(password_hash).to eq(ssh_unlock_key => "key_pass")
end

it "defaults auth_key_password to ''" do
auth.update!(:auth_key_password => nil)
cred.write_config_files

expect(password_hash).to eq(ssh_unlock_key => "")
end

context "and an existing password file" do
def existing_env_password_file(data)
cred # initialize the dir
File.write(password_file, data.to_yaml)
end

it "without the existing ssh unlock key adds the password to the file" do
existing_data = {
"^SSH [pP]assword:" => "hunter2",
"^BECOME [pP]assword:" => "hunter3"
}
expected_data = existing_data.merge(ssh_unlock_key => "key_pass")
existing_env_password_file(existing_data)
cred.write_config_files

expect(password_hash).to eq(expected_data)
end

it "with the existing data including the ssh unlock does nothing" do
existing_data = {
"^SSH [pP]assword:" => "hunter2",
"^BECOME [pP]assword:" => "hunter3",
ssh_unlock_key => "hunter4...really?"
}
existing_env_password_file(existing_data)
cred.write_config_files

expect(password_hash).to eq(existing_data)
end
end
end
end
end
end

0 comments on commit 837761c

Please sign in to comment.