Skip to content

Commit b08fcc9

Browse files
committed
Use FIPS-safe algorithms for cluster discover/join handshake
The peer discover/join handshake hardcoded the default PKCS1v15-SHA1 signing and OAEP-SHA1 encryption, which OpenSSL refuses under FIPS, causing master startup to fail with UnsupportedAlgorithm during discover_peers(). Sign sites now read self.opts["publish_signing_algorithm"] (already set to PKCS1v15-SHA224 by FIPS test fixtures). Encrypt/decrypt sites read a new master opt cluster_encryption_algorithm (default OAEP-SHA1, overridden to OAEP-SHA224 in FIPS conftest), validated against VALID_ENCRYPTION_ALGORITHMS. The two pre-existing OAEP-SHA224 hardcodes in send_aes_key_event/handle_pool_publish are folded into the same opt for consistency. Verified by running test_cluster_key_rotation in a Photon 5 container with openssl-fips-provider installed; previously errored at master startup, now passes in 67s.
1 parent 65892c3 commit b08fcc9

3 files changed

Lines changed: 73 additions & 16 deletions

File tree

salt/channel/server.py

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2427,7 +2427,7 @@ def discover_peers(self):
24272427
}
24282428
)
24292429
key = salt.crypt.PrivateKeyString(self.private_key())
2430-
sig = key.sign(tosign)
2430+
sig = key.sign(tosign, algorithm=self.opts["publish_signing_algorithm"])
24312431
data = {
24322432
"sig": sig,
24332433
"payload": tosign,
@@ -2457,7 +2457,9 @@ def send_aes_key_event(self):
24572457
hashlib.sha256(aes).hexdigest()
24582458
)
24592459
data["peers"][peer] = {
2460-
"aes": pub.encrypt(aes, algorithm="OAEP-SHA224"),
2460+
"aes": pub.encrypt(
2461+
aes, algorithm=self.opts["cluster_encryption_algorithm"]
2462+
),
24612463
"sig": self.master_key.master_key.encrypt(digest),
24622464
}
24632465
else:
@@ -2891,14 +2893,21 @@ async def handle_pool_publish(self, payload):
28912893
log.warning("Cluster join, token does not not match")
28922894
return
28932895
pubk = salt.crypt.PublicKeyString(payload["pub"])
2894-
if not pubk.verify(data["payload"], data["sig"]):
2896+
if not pubk.verify(
2897+
data["payload"],
2898+
data["sig"],
2899+
algorithm=self.opts["publish_signing_algorithm"],
2900+
):
28952901
log.warning("Cluster join signature invalid.")
28962902
return
28972903

28982904
log.info("Cluster join from %s", payload["peer_id"])
28992905
salted_secret = (
29002906
salt.crypt.PrivateKey.from_file(self.master_key.master_rsa_path)
2901-
.decrypt(payload["secret"])
2907+
.decrypt(
2908+
payload["secret"],
2909+
algorithm=self.opts["cluster_encryption_algorithm"],
2910+
)
29022911
.decode()
29032912
)
29042913

@@ -2911,7 +2920,10 @@ async def handle_pool_publish(self, payload):
29112920
log.info("Peer %s joined cluster", payload["peer_id"])
29122921
salted_aes = (
29132922
salt.crypt.PrivateKey.from_file(self.master_key.master_rsa_path)
2914-
.decrypt(payload["key"])
2923+
.decrypt(
2924+
payload["key"],
2925+
algorithm=self.opts["cluster_encryption_algorithm"],
2926+
)
29152927
.decode()
29162928
)
29172929

@@ -3025,13 +3037,20 @@ async def handle_pool_publish(self, payload):
30253037
cluster_pem_pem.encode()
30263038
)
30273039
wrapped_session = joiner_pub.encrypt(
3028-
token_bytes + cluster_key_session.encode()
3040+
token_bytes + cluster_key_session.encode(),
3041+
algorithm=self.opts["cluster_encryption_algorithm"],
30293042
)
30303043
inner_payload = {
30313044
"return_token": payload["token"],
30323045
"peer_id": self.opts["id"],
3033-
"aes": joiner_pub.encrypt(token_bytes + aes_secret),
3034-
"cluster_aes": joiner_pub.encrypt(token_bytes + cluster_aes_secret),
3046+
"aes": joiner_pub.encrypt(
3047+
token_bytes + aes_secret,
3048+
algorithm=self.opts["cluster_encryption_algorithm"],
3049+
),
3050+
"cluster_aes": joiner_pub.encrypt(
3051+
token_bytes + cluster_aes_secret,
3052+
algorithm=self.opts["cluster_encryption_algorithm"],
3053+
),
30353054
"cluster_key_session": wrapped_session,
30363055
"cluster_pem": cluster_pem_ciphertext,
30373056
"cluster_pub": cluster_pub_pem,
@@ -3051,7 +3070,9 @@ async def handle_pool_publish(self, payload):
30513070
state_sync_session_id = new_session_id()
30523071
inner_payload["state_sync_session"] = state_sync_session_id
30533072
tosign = salt.payload.package(inner_payload)
3054-
sig = salt.crypt.PrivateKeyString(self.private_key()).sign(tosign)
3073+
sig = salt.crypt.PrivateKeyString(self.private_key()).sign(
3074+
tosign, algorithm=self.opts["publish_signing_algorithm"]
3075+
)
30553076
event_data = salt.utils.event.SaltEvent.pack(
30563077
salt.utils.event.tagify("join-reply", "peer", "cluster"),
30573078
{
@@ -3080,7 +3101,11 @@ async def handle_pool_publish(self, payload):
30803101
return
30813102

30823103
cluster_pub = salt.crypt.PublicKeyString(payload["cluster_pub"])
3083-
if not cluster_pub.verify(data["payload"], data["sig"]):
3104+
if not cluster_pub.verify(
3105+
data["payload"],
3106+
data["sig"],
3107+
algorithm=self.opts["publish_signing_algorithm"],
3108+
):
30843109
log.warning("Invalid signature of cluster discover payload")
30853110
return
30863111

@@ -3101,16 +3126,20 @@ async def handle_pool_publish(self, payload):
31013126
"peer_id": self.opts["id"],
31023127
"secret": key.encrypt(
31033128
payload["token"].encode()
3104-
+ (self.opts.get("cluster_secret") or "").encode()
3129+
+ (self.opts.get("cluster_secret") or "").encode(),
3130+
algorithm=self.opts["cluster_encryption_algorithm"],
31053131
),
31063132
"key": key.encrypt(
31073133
payload["token"].encode()
3108-
+ salt.master.SMaster.secrets["aes"]["secret"].value
3134+
+ salt.master.SMaster.secrets["aes"]["secret"].value,
3135+
algorithm=self.opts["cluster_encryption_algorithm"],
31093136
),
31103137
"pub": self.public_key(),
31113138
}
31123139
)
3113-
sig = salt.crypt.PrivateKeyString(self.private_key()).sign(tosign)
3140+
sig = salt.crypt.PrivateKeyString(self.private_key()).sign(
3141+
tosign, algorithm=self.opts["publish_signing_algorithm"]
3142+
)
31143143
self.cluster_peers.append(payload["peer_id"])
31153144
event_data = salt.utils.event.SaltEvent.pack(
31163145
salt.utils.event.tagify("join", "peer", "cluster"),
@@ -3150,7 +3179,11 @@ async def handle_pool_publish(self, payload):
31503179
elif tag.startswith("cluster/peer/discover"):
31513180
payload = salt.payload.loads(data["payload"])
31523181
peer_key = salt.crypt.PublicKeyString(payload["pub"])
3153-
if not peer_key.verify(data["payload"], data["sig"]):
3182+
if not peer_key.verify(
3183+
data["payload"],
3184+
data["sig"],
3185+
algorithm=self.opts["publish_signing_algorithm"],
3186+
):
31543187
log.warning("Invalid signature of cluster discover payload")
31553188
return
31563189
log.info("Cluster discovery from %s", payload["peer_id"])
@@ -3168,7 +3201,7 @@ async def handle_pool_publish(self, payload):
31683201
}
31693202
)
31703203
key = salt.crypt.PrivateKeyString(self.cluster_key())
3171-
sig = key.sign(tosign)
3204+
sig = key.sign(tosign, algorithm=self.opts["publish_signing_algorithm"])
31723205
_ = salt.payload.package(
31733206
{
31743207
"sig": sig,
@@ -3188,7 +3221,7 @@ async def handle_pool_publish(self, payload):
31883221
aes = data["peers"][self.opts["id"]]["aes"]
31893222
sig = data["peers"][self.opts["id"]]["sig"]
31903223
key_str = self.master_key.master_key.decrypt(
3191-
aes, algorithm="OAEP-SHA224"
3224+
aes, algorithm=self.opts["cluster_encryption_algorithm"]
31923225
)
31933226
digest = salt.utils.stringutils.to_bytes(
31943227
hashlib.sha256(key_str).hexdigest()

salt/config/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,6 +1059,8 @@ def _gather_buffer_space():
10591059
"signing_algorithm": str,
10601060
# Master publish channel signing
10611061
"publish_signing_algorithm": str,
1062+
# RSA encryption used for cluster peer-to-peer messages
1063+
"cluster_encryption_algorithm": str,
10621064
# the cache driver to be used to manage keys for both minion and master
10631065
"keys.cache_driver": (type(None), str),
10641066
"request_server_ttl": int,
@@ -1773,6 +1775,7 @@ def _gather_buffer_space():
17731775
"cluster_isolated_filesystem": False,
17741776
"features": {},
17751777
"publish_signing_algorithm": "PKCS1v15-SHA1",
1778+
"cluster_encryption_algorithm": "OAEP-SHA1",
17761779
"keys.cache_driver": "localfs_key",
17771780
"request_server_aes_session": 0,
17781781
"request_server_ttl": 0,
@@ -4355,6 +4358,15 @@ def apply_master_config(overrides=None, defaults=None):
43554358
f"Please specify one of {','.join(salt.crypt.VALID_SIGNING_ALGORITHMS)}."
43564359
)
43574360

4361+
if (
4362+
opts["cluster_encryption_algorithm"]
4363+
not in salt.crypt.VALID_ENCRYPTION_ALGORITHMS
4364+
):
4365+
raise salt.exceptions.SaltConfigurationError(
4366+
f"The cluster encryption algorithm '{opts['cluster_encryption_algorithm']}' is not valid. "
4367+
f"Please specify one of {','.join(salt.crypt.VALID_ENCRYPTION_ALGORITHMS)}."
4368+
)
4369+
43584370
salt.features.setup_features(opts)
43594371
return opts
43604372

tests/pytests/integration/cluster/conftest.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ def cluster_master_1(request, salt_factories, cluster_pki_path, cluster_cache_pa
181181
"publish_signing_algorithm": (
182182
"PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1"
183183
),
184+
"cluster_encryption_algorithm": (
185+
"OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1"
186+
),
184187
}
185188
factory = salt_factories.salt_master_daemon(
186189
"127.0.0.1",
@@ -220,6 +223,9 @@ def cluster_master_2(salt_factories, cluster_master_1):
220223
"publish_signing_algorithm": (
221224
"PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1"
222225
),
226+
"cluster_encryption_algorithm": (
227+
"OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1"
228+
),
223229
}
224230

225231
# Use the same ports for both masters, they are binding to different interfaces
@@ -266,6 +272,9 @@ def cluster_master_3(salt_factories, cluster_master_1):
266272
"publish_signing_algorithm": (
267273
"PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1"
268274
),
275+
"cluster_encryption_algorithm": (
276+
"OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1"
277+
),
269278
}
270279

271280
# Use the same ports for both masters, they are binding to different interfaces
@@ -324,6 +333,9 @@ def cluster_master_4(
324333
"publish_signing_algorithm": (
325334
"PKCS1v15-SHA224" if FIPS_TESTRUN else "PKCS1v15-SHA1"
326335
),
336+
"cluster_encryption_algorithm": (
337+
"OAEP-SHA224" if FIPS_TESTRUN else "OAEP-SHA1"
338+
),
327339
}
328340

329341
# Use the same ports across the cluster; masters bind to different

0 commit comments

Comments
 (0)