[wireguard] Mark private keys sensitive, stop redacting public keys (#17736)

This commit is contained in:
J. Nick Koston
2026-07-20 20:13:56 +00:00
committed by GitHub
parent 271c1b409a
commit 500d4aa9e9
5 changed files with 97 additions and 4 deletions
+10 -2
View File
@@ -1510,10 +1510,18 @@ def _redact_with_legacy_fallback(output: str) -> str:
m = _LEGACY_REDACTION_RE.search(line)
if m is None:
continue
key = m.group("key")
if not in_substitutions:
unmarked.add(m.group("key"))
# Public keys (e.g. wireguard's peer_public_key) are not secret;
# redacting them and telling maintainers to mark them cv.sensitive
# would be wrong on both counts. Substitution keys are user-named
# with no schema behind them, so anything secret-shaped there
# (public or not) stays conservatively redacted.
if "public" in key.split("_"):
continue
unmarked.add(key)
lines[i] = (
f"{line[: m.start()]}{m.group('key')}: "
f"{line[: m.start()]}{key}: "
f"\\033[8m{m.group('val')}\\033[28m{line[m.end() :]}"
)
output = "\n".join(lines)
+2 -2
View File
@@ -74,11 +74,11 @@ CONFIG_SCHEMA = cv.All(
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
cv.Required(CONF_ADDRESS): cv.ipv4address,
cv.Optional(CONF_NETMASK, default="255.255.255.255"): cv.ipv4address,
cv.Required(CONF_PRIVATE_KEY): _wireguard_key,
cv.Required(CONF_PRIVATE_KEY): cv.sensitive(_wireguard_key),
cv.Required(CONF_PEER_ENDPOINT): cv.string,
cv.Required(CONF_PEER_PUBLIC_KEY): _wireguard_key,
cv.Optional(CONF_PEER_PORT, default=51820): cv.port,
cv.Optional(CONF_PEER_PRESHARED_KEY): _wireguard_key,
cv.Optional(CONF_PEER_PRESHARED_KEY): cv.sensitive(_wireguard_key),
cv.Optional(CONF_PEER_ALLOWED_IPS, default=["0.0.0.0/0"]): cv.ensure_list(
_cidr_network
),
@@ -0,0 +1 @@
"""Tests for the wireguard component."""
@@ -0,0 +1,44 @@
"""Tests for the wireguard component schema."""
import pytest
from esphome.components.wireguard import CONFIG_SCHEMA
from esphome.const import PlatformFramework
from esphome.yaml_util import SensitiveStr
from tests.component_tests.types import SetCoreConfigCallable
# Any 42 base64 chars plus a valid terminator satisfies _WG_KEY_REGEX.
PRIVATE_KEY = "a" * 42 + "A="
PEER_PUBLIC_KEY = "b" * 42 + "A="
PEER_PRESHARED_KEY = "c" * 42 + "A="
@pytest.mark.parametrize(
("field", "value", "sensitive"),
[
("private_key", PRIVATE_KEY, True),
("peer_preshared_key", PEER_PRESHARED_KEY, True),
("peer_public_key", PEER_PUBLIC_KEY, False),
],
)
def test_key_sensitivity(
field: str,
value: str,
sensitive: bool,
set_core_config: SetCoreConfigCallable,
) -> None:
"""The private and preshared keys are secrets and must be tagged so dump
tooling redacts them deterministically; the peer's public key is not a
secret and must stay readable in redacted dumps (see issue #17718)."""
set_core_config(PlatformFramework.ESP32_IDF)
config = CONFIG_SCHEMA(
{
"address": "10.0.0.2",
"private_key": PRIVATE_KEY,
"peer_endpoint": "wg.example.com",
"peer_public_key": PEER_PUBLIC_KEY,
"peer_preshared_key": PEER_PRESHARED_KEY,
}
)
assert isinstance(config[field], SensitiveStr) == sensitive
assert config[field] == value
+40
View File
@@ -442,6 +442,46 @@ def test_redact_with_legacy_fallback__does_not_match_fragment_as_suffix(
assert not any("legacy substring" in rec.message for rec in caplog.records)
@pytest.mark.parametrize("field", ["public_key", "peer_public_key"])
def test_redact_with_legacy_fallback__skips_public_key_fields(
field: str,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Public keys are not secret; fields with a ``public`` name segment
must pass through unredacted and without the migration warning
(see issue #17718)."""
text = f"{field}: c29tZXB1YmxpY2tleQ==\n"
with caplog.at_level(logging.WARNING, logger="esphome.__main__"):
out = _redact_with_legacy_fallback(text)
assert out == text
assert not any("legacy substring" in rec.message for rec in caplog.records)
def test_redact_with_legacy_fallback__public_substitution_still_redacted(
caplog: pytest.LogCaptureFixture,
) -> None:
"""Substitution keys are user-named with no schema behind them, so the
public-key exemption does not apply there; a ``public``-named substitution
keeps the conservative silent redaction."""
text = "substitutions:\n public_key: something\nesphome:\n name: x\n"
with caplog.at_level(logging.WARNING, logger="esphome.__main__"):
out = _redact_with_legacy_fallback(text)
assert "public_key: \\033[8msomething\\033[28m" in out
assert not any("legacy substring" in rec.message for rec in caplog.records)
def test_redact_with_legacy_fallback__public_must_be_a_whole_segment(
caplog: pytest.LogCaptureFixture,
) -> None:
"""The exemption matches ``public`` as an underscore-separated segment,
not a substring; an unrelated name like ``republic_key`` keeps the
conservative redaction."""
with caplog.at_level(logging.WARNING, logger="esphome.__main__"):
out = _redact_with_legacy_fallback("republic_key: abc\n")
assert "republic_key: \\033[8mabc\\033[28m" in out
assert any("'republic_key'" in rec.message for rec in caplog.records)
def test_redact_with_legacy_fallback__substitutions_redacted_without_warning(
caplog: pytest.LogCaptureFixture,
) -> None: