Helpful log messages for when things go wrong

Provide helpful log messages on the master and minions if a minion uses
signing or encryption that is not suppoted by the master.
This commit is contained in:
Daniel A. Wozniak 2024-06-03 12:59:54 -07:00 committed by Daniel Wozniak
parent 80a2f65e58
commit 534bf76463
4 changed files with 89 additions and 32 deletions

View file

@ -22,7 +22,7 @@ import salt.utils.minions
import salt.utils.platform
import salt.utils.stringutils
import salt.utils.verify
from salt.exceptions import SaltDeserializationError
from salt.exceptions import SaltDeserializationError, UnsupportedAlgorithm
from salt.utils.cache import CacheCli
log = logging.getLogger(__name__)
@ -237,15 +237,22 @@ class ReqServerChannel:
return pret
def _clear_signed(self, load, algorithm):
master_pem_path = os.path.join(self.opts["pki_dir"], "master.pem")
tosign = salt.payload.dumps(load)
return {
"enc": "clear",
"load": tosign,
"sig": salt.crypt.PrivateKey(master_pem_path).sign(
tosign, algorithm=algorithm
),
}
try:
master_pem_path = os.path.join(self.opts["pki_dir"], "master.pem")
tosign = salt.payload.dumps(load)
return {
"enc": "clear",
"load": tosign,
"sig": salt.crypt.PrivateKey(master_pem_path).sign(
tosign, algorithm=algorithm
),
}
except UnsupportedAlgorithm:
log.info(
"Minion tried to authenticate with unsupported signing algorithm: %s",
algorithm,
)
return {"enc": "clear", "load": {"ret": "bad sig algo"}}
def _update_aes(self):
"""
@ -678,6 +685,13 @@ class ReqServerChannel:
aes = "{}_|-{}".format(
salt.master.SMaster.secrets["aes"]["secret"].value, mtoken
)
except UnsupportedAlgorithm as exc:
log.info(
"Minion %s tried to authenticate with unsupported encryption algorithm: %s",
load["id"],
enc_algo,
)
return {"enc": "clear", "load": {"ret": "bad enc algo"}}
except Exception as exc: # pylint: disable=broad-except
log.warning("Token failed to decrypt %s", exc)
# Token failed to decrypt, send back the salty bacon to
@ -691,10 +705,17 @@ class ReqServerChannel:
try:
mtoken = self.master_key.key.decrypt(load["token"], enc_algo)
ret["token"] = pub.encrypt(mtoken, enc_algo)
except UnsupportedAlgorithm as exc:
log.info(
"Minion %s tried to authenticate with unsupported encryption algorithm: %s",
load["id"],
enc_algo,
)
return {"enc": "clear", "load": {"ret": "bad enc algo"}}
except Exception as exc: # pylint: disable=broad-except
# Token failed to decrypt, send back the salty bacon to
# support older minions
log.warning("Token failed to decrypt: %s", exc)
log.warning("Token failed to decrypt: %r", exc)
aes = salt.master.SMaster.secrets["aes"]["secret"].value
ret["aes"] = pub.encrypt(aes, enc_algo)

View file

@ -40,6 +40,7 @@ from salt.exceptions import (
MasterExit,
SaltClientError,
SaltReqTimeoutError,
UnsupportedAlgorithm,
)
try:
@ -238,19 +239,27 @@ class PrivateKey(BaseKey):
def sign(self, data, algorithm=PKCS1v15_SHA1):
_padding = self.parse_padding_for_signing(algorithm)
_hash = self.parse_hash(algorithm)
return self.key.sign(salt.utils.stringutils.to_bytes(data), _padding(), _hash())
try:
return self.key.sign(
salt.utils.stringutils.to_bytes(data), _padding(), _hash()
)
except cryptography.exceptions.UnsupportedAlgorithm:
raise UnsupportedAlgorithm(f"Unsupported algorithm: {algorithm}")
def decrypt(self, data, algorithm=OAEP_SHA1):
_padding = self.parse_padding_for_encryption(algorithm)
_hash = self.parse_hash(algorithm)
return self.key.decrypt(
data,
_padding(
mgf=padding.MGF1(algorithm=_hash()),
algorithm=_hash(),
label=None,
),
)
try:
return self.key.decrypt(
data,
_padding(
mgf=padding.MGF1(algorithm=_hash()),
algorithm=_hash(),
label=None,
),
)
except cryptography.exceptions.UnsupportedAlgorithm:
raise UnsupportedAlgorithm(f"Unsupported algorithm: {algorithm}")
class PublicKey(BaseKey):
@ -265,14 +274,17 @@ class PublicKey(BaseKey):
_padding = self.parse_padding_for_encryption(algorithm)
_hash = self.parse_hash(algorithm)
bdata = salt.utils.stringutils.to_bytes(data)
return self.key.encrypt(
bdata,
_padding(
mgf=padding.MGF1(algorithm=_hash()),
algorithm=_hash(),
label=None,
),
)
try:
return self.key.encrypt(
bdata,
_padding(
mgf=padding.MGF1(algorithm=_hash()),
algorithm=_hash(),
label=None,
),
)
except cryptography.exceptions.UnsupportedAlgorithm:
raise UnsupportedAlgorithm(f"Unsupported algorithm: {algorithm}")
def verify(self, data, signature, algorithm=PKCS1v15_SHA1):
_padding = self.parse_padding_for_signing(algorithm)
@ -752,6 +764,18 @@ class AsyncAuth:
"Authentication wait time is %s", acceptance_wait_time
)
continue
elif creds == "bad enc algo":
log.error(
"This minion is using a encryption algorithm that is "
"not supported by it's Master. Please check your minion configutation."
)
break
elif creds == "bad sig algo":
log.error(
"This minion is using a signing algorithm that is "
"not supported by it's Master. Please check your minion configutation."
)
break
break
if not isinstance(creds, dict) or "aes" not in creds:
if self.opts.get("detect_mode") is True:
@ -853,6 +877,13 @@ class AsyncAuth:
if not isinstance(payload, dict) or "load" not in payload:
log.error("Sign-in attempt failed: %s", payload)
return False
elif isinstance(payload["load"], dict) and "ret" in payload["load"]:
if payload["load"]["ret"] == "bad enc algo":
log.error("Sign-in attempt failed: %s", payload)
return "bad enc algo"
elif payload["load"]["ret"] == "bad sig algo":
log.error("Sign-in attempt failed: %s", payload)
return "bad sig algo"
clear_signed_data = payload["load"]
clear_signature = payload["sig"]

View file

@ -362,6 +362,12 @@ class AuthorizationError(SaltException):
"""
class UnsupportedAlgorithm(SaltException):
"""
Thrown when a requested encryption or signing algorithm is un-supported.
"""
class SaltDaemonNotRunning(SaltException):
"""
Throw when a running master/minion/syndic is not running but is needed to

View file

@ -2,7 +2,6 @@ import hashlib
import hmac
import os
import cryptography.exceptions
import pytest
from cryptography.hazmat.backends.openssl import backend
from cryptography.hazmat.primitives import serialization
@ -344,7 +343,7 @@ def test_loading_encrypted_openssl_format(openssl_encrypted_key, passphrase, tmp
@pytest.mark.skipif(not FIPS_TESTRUN, reason="Only valid when in FIPS mode")
def test_fips_bad_signing_algo(private_key, passphrase):
key = salt.crypt.PrivateKey(private_key, passphrase)
with pytest.raises(cryptography.exceptions.UnsupportedAlgorithm):
with pytest.raises(salt.exceptions.UnsupportedAlgorithm):
key.sign("meh", salt.crypt.PKCS1v15_SHA1)
@ -361,7 +360,7 @@ def test_fips_bad_signing_algo_verification(private_key, passphrase):
@pytest.mark.skipif(not FIPS_TESTRUN, reason="Only valid when in FIPS mode")
def test_fips_bad_encryption_algo(private_key, passphrase):
key = salt.crypt.PublicKey(private_key.replace(".pem", ".pub"))
with pytest.raises(cryptography.exceptions.UnsupportedAlgorithm):
with pytest.raises(salt.exceptions.UnsupportedAlgorithm):
key.encrypt("meh", salt.crypt.OAEP_SHA1)
@ -370,5 +369,5 @@ def test_fips_bad_decryption_algo(private_key, passphrase):
pubkey = LegacyPublicKey(private_key.replace(".pem", ".pub"))
data = pubkey.encrypt("meh")
key = salt.crypt.PrivateKey(private_key, passphrase)
with pytest.raises(cryptography.exceptions.UnsupportedAlgorithm):
with pytest.raises(salt.exceptions.UnsupportedAlgorithm):
key.decrypt(data)