Skip to content

add ansible-lint rules to ensure no static secrets and empty defaults are present in the code#526

Open
evgeni wants to merge 9 commits into
masterfrom
lint-empty-defaults
Open

add ansible-lint rules to ensure no static secrets and empty defaults are present in the code#526
evgeni wants to merge 9 commits into
masterfrom
lint-empty-defaults

Conversation

@evgeni
Copy link
Copy Markdown
Member

@evgeni evgeni commented May 28, 2026

Why are you introducing these changes? (Problem description, related links)

#523 (review)

What are the changes introduced in this pull request?

How to test this pull request

Steps to reproduce:

Checklist

  • Tests added/updated (if applicable)
  • Documentation updated (if applicable)

@evgeni evgeni force-pushed the lint-empty-defaults branch from 8ed56d7 to 93be9bb Compare May 28, 2026 06:44
Comment thread .ansible-lint-rules/empty_defaults.py Outdated
Comment thread .ansible-lint-rules/empty_defaults.py Outdated
Comment on lines +62 to +76
def test_empty_defaults_are_flagged(
config_options: Options,
app: object,
) -> None:
"""Null and empty string defaults produce match errors."""
rules = RulesCollection(app=app, options=config_options)
rules.register(EmptyDefaultsRule())
results = Runner(
Lintable("tests/fixtures/ansible-lint/roles/test_empty_defaults"),
rules=rules,
).run()
empty_results = [r for r in results if r.rule.id == EmptyDefaultsRule.id]
assert len(empty_results) == 4
for result in empty_results:
assert result.tag == "var-defaults[no-empty]"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but isn't this only happy path testing and this but doesn't explicitly verify that edge cases are correctly NOT flagged.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which edge cases? tests/fixtures/ansible-lint/roles/test_empty_defaults/defaults/main.yml contains 9 variables, and the assertion is that we flag 4 of them as "empty" (the ones that are either null or ""), but others (e.g. false and []) are accepted

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I see your point with that current cases. 🙌

return results

for key, value in meta_data.items():
if value is None or value == "":
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So just to clarify, The test fixture has passed test_empty_defaults_plugins: [] which is not flagged right. Shouldn't empty lists ([]) also be checked in that case? Or is empty list a valid default?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've kept [] as allowed, as my trail of thought was: "this is usually used for defining additions (e.g. plugins), so an empty list is ok"

Copy link
Copy Markdown
Contributor

@archanaserver archanaserver left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped comments just to clarify some thought on this.

@ehelms
Copy link
Copy Markdown
Member

ehelms commented May 28, 2026

This is from me testing out a review skill I wrote:

empty_defaults.py and no_static_secrets.py — redundant YAML parse

  • .ansible-lint-rules/empty_defaults.py:36 — parse_yaml_from_file(str(file.path)) re-parses a file that file.data already holds. The rule already guards against empty data with if not file.data, so meta_data = dict(file.data) is sufficient. Same pattern in no_static_secrets.py:51.

no_static_secrets rule: None/empty secret values slip through

  • .ansible-lint-rules/no_static_secrets.py:50 — The rule only matches isinstance(value, str) and not has_jinja(value). A null value for a secret variable (e.g., some_password:) is not flagged. Role defaults will catch it via var-defaults[no-empty], but non-role vars files with a null secret variable are silently ignored. Low risk given how foremanctl uses secrets, but worth noting.

@evgeni
Copy link
Copy Markdown
Member Author

evgeni commented May 29, 2026

empty_defaults.py and no_static_secrets.py — redundant YAML parse

  • .ansible-lint-rules/empty_defaults.py:36 — parse_yaml_from_file(str(file.path)) re-parses a file that file.data already holds. The rule already guards against empty data with if not file.data, so meta_data = dict(file.data) is sufficient. Same pattern in no_static_secrets.py:51.

Lintable.data uses parse_yaml_linenumbers, not parse_yaml_from_file, and while the tests do pass after the change to file.data, I am not versed enough in ansible-lint internals and would follow the existing core rules like var-naming which use parse_yaml_from_file explicitly

@evgeni evgeni force-pushed the lint-empty-defaults branch 3 times, most recently from b950c1f to f6a48ab Compare May 29, 2026 07:58
Comment on lines -25 to -26
candlepin_database_ssl_cert:
candlepin_database_ssl_key:
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

love finding unused vars :)

self.create_matcherror(
message=f"Role default variable '{key}' has an empty value. Use `undef(hint='…')` to indicate defaults that need to be overriden.",
filename=file,
tag="var-defaults[no-empty]",
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this uses id[sub-id] style, as I've used var-naming rule as a base, and this has multiple sub-tags.
after using the two rules in this PR (and also flagging certain vars as noqa for them), I think this is wrong.

We should either:

  • Have a single var-content rule file with multiple tags (var-content[no-empty-defaults], var-content[no-static-secrets])
  • Have dedicated named rules like no-empty-defaults and no-static-secrets

I slightly lean towards the dedicated naming, but eager to hear what you think.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dedicated looks good to me

@evgeni evgeni changed the title add a var-defaults[no-empty] check that ensures no defaults are empty add ansible-lint rules to ensure no static secrets and empty defaults are present in the code May 29, 2026
@evgeni evgeni force-pushed the lint-empty-defaults branch from f6a48ab to 7b0048c Compare May 29, 2026 08:54
@evgeni
Copy link
Copy Markdown
Member Author

evgeni commented May 29, 2026

no_static_secrets rule: None/empty secret values slip through

  • .ansible-lint-rules/no_static_secrets.py:50 — The rule only matches isinstance(value, str) and not has_jinja(value). A null value for a secret variable (e.g., some_password:) is not flagged. Role defaults will catch it via var-defaults[no-empty], but non-role vars files with a null secret variable are silently ignored. Low risk given how foremanctl uses secrets, but worth noting.

I guess null is static by definition, but do we really need to care?
OTOH, adding it to the linter doesn't cost much, so 🤷‍♀️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants