Skip to content

Commit bea2b37

Browse files
committed
Fixes #37418 - Fixes an issue that caused hidden Ansible variables to 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
1 parent 6cd6fe5 commit bea2b37

File tree

9 files changed

+85
-18
lines changed

9 files changed

+85
-18
lines changed

app/controllers/api/v2/ansible_inventories_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def schedule_params
9999

100100
def show_inventory(ids_key, condition_key)
101101
ids = params.fetch(ids_key, []).uniq
102-
render :json => ForemanAnsible::InventoryCreator.new(Host.where(condition_key => ids)).to_hash.to_json
102+
render :json => ForemanAnsible::InventoryCreator.new(Host.where(condition_key => ids)).to_hash_with_secrets_redacted.to_json
103103
end
104104
end
105105
end

app/graphql/presenters/overriden_ansible_variable_presenter.rb

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,34 @@ module Presenters
22
class OverridenAnsibleVariablePresenter
33
attr_reader :ansible_variable
44

5-
delegate :id, :key, :description, :override?,
6-
:parameter_type, :hidden_value?, :omit, :required,
7-
:validator_type, :validator_rule, :default_value,
8-
:ansible_role, :current_value, :to => :ansible_variable
5+
delegate :id, :key, :description,
6+
:parameter_type, :omit, :required,
7+
:validator_type, :validator_rule,
8+
:ansible_role, :to => :ansible_variable
9+
10+
def default_value
11+
ansible_variable.editable_by_user? ? ansible_variable.default_value : '*****'
12+
end
13+
14+
def hidden_value
15+
ansible_variable.hidden_value?
16+
end
17+
18+
def override
19+
ansible_variable.override?
20+
end
921

1022
def initialize(ansible_variable, override_resolver)
1123
@ansible_variable = ansible_variable
1224
@override_resolver = override_resolver
1325
end
1426

1527
def current_value
16-
@override_resolver.resolve @ansible_variable
28+
resolved = @override_resolver.resolve @ansible_variable
29+
unless resolve.nil?
30+
resolved[:value] = '*****' unless ansible_variable.editable_by_user?
31+
end
32+
resolved
1733
end
1834
end
1935
end

app/services/foreman_ansible/ansible_info.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
module ForemanAnsible
22
class AnsibleInfo < ::HostInfo::Provider
3-
def host_info
4-
{ 'parameters' => ansible_params }
3+
def host_info(redact_secrets = false)
4+
{ 'parameters' => ansible_params(redact_secrets) }
55
end
66

7-
def ansible_params
7+
def ansible_params(redact_secrets = false)
88
variables = AnsibleVariable.where(:ansible_role_id => host.all_ansible_roles.pluck(:id), :override => true)
99
values = variables.values_hash(host)
1010

1111
variables.each_with_object({}) do |var, memo|
1212
value = values[var]
13-
memo[var.key] = value unless value.nil?
13+
unless value.nil?
14+
memo[var.key] = redact_secrets && var.hidden_value? ? var.hidden_value : value
15+
end
1416
memo
1517
end
1618
end

app/services/foreman_ansible/inventory_creator.rb

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,32 @@ def initialize(hosts, template_invocation = nil)
1919
# more advanced cases). Therefore we have only the 'all' group
2020
# with all hosts.
2121
def to_hash
22+
to_hash_with_secrets_redacted(false)
23+
end
24+
25+
def to_hash_with_secrets_redacted(redact_secrets = true)
2226
hosts = @hosts.map(&:name)
2327

2428
{ 'all' => { 'hosts' => hosts,
2529
'vars' => template_inputs(@template_invocation) },
26-
'_meta' => { 'hostvars' => hosts_vars } }
30+
'_meta' => { 'hostvars' => hosts_vars(redact_secrets) } }
2731
end
2832

29-
def hosts_vars
33+
def hosts_vars(redact_secrets = false)
3034
hosts.reduce({}) do |hash, host|
3135
hash.update(
32-
host.name => host_vars(host)
36+
host.name => host_vars(host, redact_secrets)
3337
)
3438
end
3539
end
3640

37-
def host_vars(host)
41+
def host_vars(host, redact_secrets = false)
3842
{
3943
'foreman' => reduced_host_info(host).fetch('parameters', {}),
4044
'foreman_ansible_roles' => host_roles(host)
4145
}.merge(connection_params(host)).
4246
merge(host_params(host)).
43-
merge(ansible_params(host))
47+
merge(ansible_params(host, redact_secrets))
4448
end
4549

4650
def connection_params(host)
@@ -62,8 +66,8 @@ def host_roles(host)
6266
host.all_ansible_roles.map(&:name)
6367
end
6468

65-
def ansible_params(host)
66-
ForemanAnsible::AnsibleInfo.new(host).ansible_params
69+
def ansible_params(host, redact_secrets = false)
70+
ForemanAnsible::AnsibleInfo.new(host).ansible_params(redact_secrets)
6771
end
6872

6973
def reduced_host_info(host)

webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import AnsibleHostInventory from './AnsibleHostInventory';
99
import ErrorState from '../../../ErrorState';
1010

1111
const WrappedAnsibleHostInventory = ({ hostId }) => {
12-
const params = useMemo(() => ({ params: { host_ids: [hostId] } }), [hostId]);
12+
const params = useMemo(
13+
() => ({ params: { host_ids: [hostId], redact_secrets: true } }),
14+
[hostId]
15+
);
1316

1417
const url = hostId && foremanUrl('/ansible/api/ansible_inventories/hosts');
1518
const { response: inventory, status } = useAPI('get', url, params);

webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTableHelper.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import { TextInput } from '@patternfly/react-core';
23
import { TimesIcon, CheckIcon } from '@patternfly/react-icons';
34
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
45

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

26+
if (variable.hiddenValue) {
27+
return (
28+
<TextInput
29+
value="Not the secrets you're looking for..."
30+
type="password"
31+
aria-label="hidden ansible variable"
32+
isDisabled
33+
style={{
34+
'--pf-c-form-control--BackgroundColor': 'white',
35+
color: 'black',
36+
}}
37+
/>
38+
);
39+
}
40+
2541
switch (variable.parameterType) {
2642
case 'boolean':
2743
return value ? <CheckIcon /> : <TimesIcon />;

webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.fixtures.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const withFqdnOverride = canEdit => ({
3636
validatorType: '',
3737
validatorRule: null,
3838
required: false,
39+
hiddenValue: false,
3940
lookupValues: {
4041
nodes: [
4142
{
@@ -70,6 +71,7 @@ const withDomainOverride = canEdit => ({
7071
validatorType: '',
7172
validatorRule: null,
7273
required: false,
74+
hiddenValue: false,
7375
lookupValues: {
7476
nodes: [],
7577
},
@@ -142,6 +144,7 @@ export const mocks = [
142144
validatorType: 'list',
143145
validatorRule: 'a,b,c',
144146
required: true,
147+
hiddenValue: false,
145148
lookupValues: {
146149
nodes: [
147150
{
@@ -170,6 +173,7 @@ export const mocks = [
170173
validatorType: '',
171174
validatorRule: null,
172175
required: false,
176+
hiddenValue: false,
173177
lookupValues: {
174178
nodes: [],
175179
},
@@ -190,6 +194,7 @@ export const mocks = [
190194
validatorType: '',
191195
validatorRule: null,
192196
required: false,
197+
hiddenValue: false,
193198
lookupValues: {
194199
nodes: [],
195200
},
@@ -215,6 +220,7 @@ export const mocks = [
215220
validatorType: '',
216221
validatorRule: null,
217222
required: false,
223+
hiddenValue: true,
218224
lookupValues: {
219225
nodes: [],
220226
},
@@ -240,6 +246,7 @@ export const mocks = [
240246
validatorType: '',
241247
validatorRule: null,
242248
required: false,
249+
hiddenValue: false,
243250
lookupValues: {
244251
nodes: [],
245252
},
@@ -260,6 +267,7 @@ export const mocks = [
260267
validatorType: '',
261268
validatorRule: null,
262269
required: false,
270+
hiddenValue: true,
263271
lookupValues: {
264272
nodes: [],
265273
},
@@ -282,6 +290,7 @@ export const mocks = [
282290
validatorType: '',
283291
validatorRule: null,
284292
required: false,
293+
hiddenValue: true,
285294
lookupValues: {
286295
nodes: [],
287296
},

webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,20 @@ describe('AnsibleVariableOverrides', () => {
7070
const actions = screen.queryAllByRole('button', { name: 'Actions' });
7171
expect(actions).toHaveLength(0);
7272
});
73+
it('should hide hidden values', async () => {
74+
const { container } = render(
75+
<TestComponent
76+
hostId={hostId}
77+
mocks={mocks}
78+
hostAttrs={hostAttrs}
79+
history={historyMock}
80+
/>
81+
);
82+
await waitFor(tick);
83+
expect(screen.getByText('ellipse')).toBeInTheDocument();
84+
expect(screen.getByText('sun')).toBeInTheDocument();
85+
expect(screen.getByText('moon')).toBeInTheDocument();
86+
// number of hidden variables + 1 for pagination input
87+
expect(container.getElementsByTagName('input')).toHaveLength(3 + 1);
88+
});
7389
});

webpack/graphql/queries/hostVariableOverrides.gql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ query($id: String!, $match: String, $first: Int, $last: Int) {
1818
validatorType
1919
validatorRule
2020
required
21+
hiddenValue
2122
lookupValues(match: $match) {
2223
nodes {
2324
id

0 commit comments

Comments
 (0)