Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions projects/rdc/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ Full documentation for RDC is available at [ROCm DataCenter Tool User Guide](htt

- Opening or stopping an RDC handle no longer crashes the process when another handle is in use, and concurrent `rdc_connect()` calls no longer fail at random.

- Certificates signed with the RDC certificate authority setup in `authentication/openssl.cnf` can no longer sign other certificates. A certificate request could ask to become a signing certificate, and its holder could then create client and server certificates that `rdcd` and `rdci` trust.
- If you signed requests that other people created, check the certificates in the `newcerts` directory of your CA for `CA:TRUE`. If you find one, create a new root certificate and reissue all certificates.

- `rdci discovery` now reports an error instead of exiting successfully with an incomplete GPU list when `rdcd` cannot read a GPU's attributes. Failed `rdc_device_get_attributes()` calls now return zeroed attributes, and `rdcd` replies to failed requests no longer contain garbage values.

- `rdci dmon` and `rdci health -s` name the missing group again, for example `Cannot find the group 4242`. Since ROCm 7.12 they printed only `Group not found` (or `Fieldgroup not found` for a missing field group in `rdci dmon`).
Expand Down
21 changes: 18 additions & 3 deletions projects/rdc/authentication/openssl.cnf
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,17 @@ default_days = 365
default_md = sha512
preserve = no
email_in_dn = no
nameopt = default_ca
certopt = default_ca
# Show the whole certificate, extensions included, before asking whether to
# sign it. OpenSSL ignores "nameopt"/"certopt" and then shows no extensions.
name_opt = ca_default
cert_opt = ca_default
policy = policy_match
unique_subject = no
copy_extensions = copyall
# 'copy' only copies request extensions the CA has not already set, so the
# x509_extensions below win. With 'copyall' a request can overwrite them
# and have itself issued as a signing certificate.
copy_extensions = copy
x509_extensions = usr_cert

[ policy_match ]
countryName = match
Expand Down Expand Up @@ -70,6 +76,15 @@ countryName_default = MC
# wildcards are acceptable for domain; e.g., *.amd.com
commonName_default = Mydomain

[ usr_cert ]
# Applied by the CA to every certificate it issues. These are set here rather
# than taken from the request so a requester cannot choose them for itself.
basicConstraints = critical, CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth, clientAuth
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer

[ v3_ca ]
basicConstraints = CA:TRUE
subjectKeyIdentifier = hash
Expand Down
101 changes: 101 additions & 0 deletions projects/rdc/tests/ci/test_authentication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""Checks of the certificate authority settings in authentication/openssl.cnf.

Runs the openssl commands of 01gen_root_cert.sh and 02gen_ssl_artifacts.sh in a
temporary directory, answering the signing prompts like the readme says. Needs only
the openssl command line tool (no GPU, no daemon).
"""

import os
import shutil
import subprocess

import pytest

CNF = os.path.realpath(
os.path.join(os.path.dirname(__file__), "..", "..", "authentication", "openssl.cnf")
)
# Must match the CA, see [ policy_match ] in openssl.cnf.
SUBJECT = "/O=MyCompany/OU=MyCompanyUnit/L=MyCity/ST=MyStateProvince/C=MC"
# A request that asks to become a signing certificate.
SIGNING_REQUEST_CNF = """\
[ req ]
string_mask = nombstr
distinguished_name = dn
req_extensions = ext
[ dn ]
[ ext ]
basicConstraints = critical, CA:TRUE
keyUsage = critical, keyCertSign, digitalSignature
"""

pytestmark = pytest.mark.skipif(shutil.which("openssl") is None, reason="openssl not found")


def _openssl(cwd, *args, answers=None):
return subprocess.run(
["openssl", *args],
cwd=cwd,
input=answers,
capture_output=True,
text=True,
timeout=120,
check=True,
)


@pytest.fixture(scope="module")
def ca_dir(tmp_path_factory):
"""A CA directory set up the way 01gen_root_cert.sh sets it up."""
root = tmp_path_factory.mktemp("rdc_ca")
shutil.copy(CNF, root / "openssl.cnf")
(root / "signing_request.cnf").write_text(SIGNING_REQUEST_CNF)
ca = root / "CA"
(ca / "private").mkdir(parents=True)
(ca / "newcerts").mkdir()
(ca / "serial").write_text("01\n")
(ca / "index.txt").write_text("")
_openssl(
ca, "req", "-new", "-x509", "-nodes", "-newkey", "rsa:2048", "-extensions", "v3_ca",
"-keyout", "private/rdc_cakey.pem", "-out", "rdc_cacert.pem", "-days", "30",
"-config", "../openssl.cnf", "-subj", SUBJECT + "/CN=rdc-test-root",
) # fmt: skip
return ca


def _issue(ca, name, request_cnf):
"""Create a request with request_cnf and sign it like 02gen_ssl_artifacts.sh."""
_openssl(
ca, "req", "-new", "-nodes", "-newkey", "rsa:2048", "-keyout", name + ".key",
"-out", name + "_csr.pem", "-config", request_cnf, "-subj", SUBJECT + "/CN=" + name,
) # fmt: skip
signing = _openssl(
ca, "ca", "-out", name + ".pem", "-config", "../openssl.cnf", "-infiles", name + "_csr.pem",
answers="y\ny\n",
) # fmt: skip
cert = _openssl(ca, "x509", "-in", name + ".pem", "-noout", "-text").stdout
return cert, signing.stdout + signing.stderr


def test_request_cannot_make_itself_a_signing_certificate(ca_dir):
cert, _ = _issue(ca_dir, "asks-for-ca", "../signing_request.cnf")
assert "CA:FALSE" in cert
assert "CA:TRUE" not in cert
assert "Certificate Sign" not in cert


def test_shipped_request_keeps_its_alternative_names(ca_dir):
cert, _ = _issue(ca_dir, "shipped", "../openssl.cnf")
assert "CA:FALSE" in cert
assert "DNS:localhost" in cert


def test_issued_certificate_is_for_tls_servers_and_clients(ca_dir):
cert, _ = _issue(ca_dir, "tls", "../openssl.cnf")
assert "TLS Web Server Authentication" in cert
assert "TLS Web Client Authentication" in cert


def test_signing_prompt_shows_the_extensions(ca_dir):
_, prompt = _issue(ca_dir, "prompted", "../openssl.cnf")
assert "X509v3 Subject Alternative Name" in prompt
assert "Sign the certificate?" in prompt
Loading