Skip to content

Commit

Permalink
Fixes #37418 - Fixes an issue that caused hidden Ansible variables to…
Browse files Browse the repository at this point in the history
… be shown in plain text

on the Host-Details page

- Add "hiddenValue" to GraphQL query hostVariableOverrides.gql
- Replace plain text secret with masked value
- Adds a parameter "redact_secrets" to AnsibleInventoriesController#show_inventory
- Change frontend code to use newly added "redact_secrets" parameter
- Add a new "to_hash_with_secrets_redacted" method to InventoryCreator
- Hide hidden values in GQL response by if edit_ansible_variables not granted
  • Loading branch information
Thorben-D committed May 22, 2024
1 parent 8c6be4f commit 0792fb6
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 18 deletions.
2 changes: 1 addition & 1 deletion app/controllers/api/v2/ansible_inventories_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def schedule_params

def show_inventory(ids_key, condition_key)
ids = params.fetch(ids_key, []).uniq
render :json => ForemanAnsible::InventoryCreator.new(Host.where(condition_key => ids)).to_hash.to_json
render :json => ForemanAnsible::InventoryCreator.new(Host.where(condition_key => ids)).to_hash_with_secrets_redacted.to_json
end
end
end
Expand Down
24 changes: 19 additions & 5 deletions app/graphql/presenters/overriden_ansible_variable_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,32 @@ module Presenters
class OverridenAnsibleVariablePresenter
attr_reader :ansible_variable

delegate :id, :key, :description, :override?,
:parameter_type, :hidden_value?, :omit, :required,
:validator_type, :validator_rule, :default_value,
:ansible_role, :current_value, :to => :ansible_variable
delegate :id, :key, :description,
:parameter_type, :omit, :required,
:validator_type, :validator_rule,
:ansible_role, :to => :ansible_variable

def default_value
ansible_variable.editable_by_user? ? ansible_variable.default_value : '*****'
end

def hidden_value
ansible_variable.hidden_value?
end

def override
ansible_variable.override?
end

def initialize(ansible_variable, override_resolver)
@ansible_variable = ansible_variable
@override_resolver = override_resolver
end

def current_value
@override_resolver.resolve @ansible_variable
resolved = @override_resolver.resolve @ansible_variable
resolved[:value] = '*****' unless ansible_variable.editable_by_user?
resolved
end
end
end
10 changes: 6 additions & 4 deletions app/services/foreman_ansible/ansible_info.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
module ForemanAnsible
class AnsibleInfo < ::HostInfo::Provider
def host_info
{ 'parameters' => ansible_params }
def host_info(redact_secrets = false)
{ 'parameters' => ansible_params(redact_secrets) }
end

def ansible_params
def ansible_params(redact_secrets = false)
variables = AnsibleVariable.where(:ansible_role_id => host.all_ansible_roles.pluck(:id), :override => true)
values = variables.values_hash(host)

variables.each_with_object({}) do |var, memo|
value = values[var]
memo[var.key] = value unless value.nil?
unless value.nil?
memo[var.key] = redact_secrets && var.hidden_value? ? var.hidden_value : value
end
memo
end
end
Expand Down
18 changes: 11 additions & 7 deletions app/services/foreman_ansible/inventory_creator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,32 @@ def initialize(hosts, template_invocation = nil)
# more advanced cases). Therefore we have only the 'all' group
# with all hosts.
def to_hash
to_hash_with_secrets_redacted(false)
end

def to_hash_with_secrets_redacted(redact_secrets = true)
hosts = @hosts.map(&:name)

{ 'all' => { 'hosts' => hosts,
'vars' => template_inputs(@template_invocation) },
'_meta' => { 'hostvars' => hosts_vars } }
'_meta' => { 'hostvars' => hosts_vars(redact_secrets) } }
end

def hosts_vars
def hosts_vars(redact_secrets = false)
hosts.reduce({}) do |hash, host|
hash.update(
host.name => host_vars(host)
host.name => host_vars(host, redact_secrets)
)
end
end

def host_vars(host)
def host_vars(host, redact_secrets = false)
{
'foreman' => reduced_host_info(host).fetch('parameters', {}),
'foreman_ansible_roles' => host_roles(host)
}.merge(connection_params(host)).
merge(host_params(host)).
merge(ansible_params(host))
merge(ansible_params(host, redact_secrets))
end

def connection_params(host)
Expand All @@ -62,8 +66,8 @@ def host_roles(host)
host.all_ansible_roles.map(&:name)
end

def ansible_params(host)
ForemanAnsible::AnsibleInfo.new(host).ansible_params
def ansible_params(host, redact_secrets = false)
ForemanAnsible::AnsibleInfo.new(host).ansible_params(redact_secrets)
end

def reduced_host_info(host)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import AnsibleHostInventory from './AnsibleHostInventory';
import ErrorState from '../../../ErrorState';

const WrappedAnsibleHostInventory = ({ hostId }) => {
const params = useMemo(() => ({ params: { host_ids: [hostId] } }), [hostId]);
const params = useMemo(
() => ({ params: { host_ids: [hostId], redact_secrets: true } }),
[hostId]
);

const url = hostId && foremanUrl('/ansible/api/ansible_inventories/hosts');
const { response: inventory, status } = useAPI('get', url, params);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { TextInput } from '@patternfly/react-core';
import { TimesIcon, CheckIcon } from '@patternfly/react-icons';
import { sprintf, translate as __ } from 'foremanReact/common/I18n';

Expand All @@ -22,6 +23,21 @@ export const formatValue = variable => {
? variable.currentValue.value
: variable.defaultValue;

if (variable.hiddenValue) {
return (
<TextInput
value="Not the secrets you're looking for..."
type="password"
aria-label="hidden ansible variable"
isDisabled
style={{
'--pf-c-form-control--BackgroundColor': 'white',
color: 'black',
}}
/>
);
}

switch (variable.parameterType) {
case 'boolean':
return value ? <CheckIcon /> : <TimesIcon />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const withFqdnOverride = canEdit => ({
validatorType: '',
validatorRule: null,
required: false,
hiddenValue: false,
lookupValues: {
nodes: [
{
Expand Down Expand Up @@ -70,6 +71,7 @@ const withDomainOverride = canEdit => ({
validatorType: '',
validatorRule: null,
required: false,
hiddenValue: false,
lookupValues: {
nodes: [],
},
Expand Down Expand Up @@ -142,6 +144,7 @@ export const mocks = [
validatorType: 'list',
validatorRule: 'a,b,c',
required: true,
hiddenValue: false,
lookupValues: {
nodes: [
{
Expand Down Expand Up @@ -170,6 +173,7 @@ export const mocks = [
validatorType: '',
validatorRule: null,
required: false,
hiddenValue: false,
lookupValues: {
nodes: [],
},
Expand All @@ -190,6 +194,7 @@ export const mocks = [
validatorType: '',
validatorRule: null,
required: false,
hiddenValue: false,
lookupValues: {
nodes: [],
},
Expand All @@ -215,6 +220,7 @@ export const mocks = [
validatorType: '',
validatorRule: null,
required: false,
hiddenValue: true,
lookupValues: {
nodes: [],
},
Expand All @@ -240,6 +246,7 @@ export const mocks = [
validatorType: '',
validatorRule: null,
required: false,
hiddenValue: false,
lookupValues: {
nodes: [],
},
Expand All @@ -260,6 +267,7 @@ export const mocks = [
validatorType: '',
validatorRule: null,
required: false,
hiddenValue: true,
lookupValues: {
nodes: [],
},
Expand All @@ -282,6 +290,7 @@ export const mocks = [
validatorType: '',
validatorRule: null,
required: false,
hiddenValue: true,
lookupValues: {
nodes: [],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,20 @@ describe('AnsibleVariableOverrides', () => {
const actions = screen.queryAllByRole('button', { name: 'Actions' });
expect(actions).toHaveLength(0);
});
it('should hide hidden values', async () => {
const { container } = render(
<TestComponent
hostId={hostId}
mocks={mocks}
hostAttrs={hostAttrs}
history={historyMock}
/>
);
await waitFor(tick);
expect(screen.getByText('ellipse')).toBeInTheDocument();
expect(screen.getByText('sun')).toBeInTheDocument();
expect(screen.getByText('moon')).toBeInTheDocument();
// number of hidden variables + 1 for pagination input
expect(container.getElementsByTagName('input')).toHaveLength(3 + 1);
});
});
1 change: 1 addition & 0 deletions webpack/graphql/queries/hostVariableOverrides.gql
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ query($id: String!, $match: String, $first: Int, $last: Int) {
validatorType
validatorRule
required
hiddenValue
lookupValues(match: $match) {
nodes {
id
Expand Down

0 comments on commit 0792fb6

Please sign in to comment.