diff --git a/.gitignore b/.gitignore index b54b2fb..b98d43c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ /tests/tmp_merge_coveragerc /tests/total-*coveragedata /.tox +/.vscode diff --git a/README.md b/README.md index e49728b..ba1f019 100644 --- a/README.md +++ b/README.md @@ -409,6 +409,58 @@ kernel and device, changing some features might not be supported. txvlan: yes|no # optional ``` +### `802.1x` + +Configures 802.1x authentication for an interface. + +Currently, NetworkManager is the only supported provider and EAP-TLS is the only supported EAP method. + +SSL certificates and keys must be deployed on the host prior to running the role. + +* `eap` + + The allowed EAP method to be used when authenticating to the network with 802.1x. + + Currently, `tls` is the default and the only accepted value. + +* `identity` (required) + + Identity string for EAP authentication methods. + +* `private-key` (required) + + Absolute path to the client's PEM or PKCS#12 encoded private key used for 802.1x authentication. + + * `private-key-password` + + Password to the private key specified in `private-key`. + + * `private-key-password-flags` + + List of flags to configure how the private key password is managed. + + Multiple flags may be specified. + + Valid flags are: + - `none` + - `agent-owned` + - `not-saved` + - `not-required` + + See NetworkManager documentation on "Secret flag types" more details (`man 5 nm-settings`). + + * `client-cert` (required) + + Absolute path to the client's PEM encoded certificate used for 802.1x authentication. + + * `ca-cert` + + Absolute path to the PEM encoded certificate authority used to verify the EAP server. + + * `system-ca-certs` + + If set to `True`, NetworkManager will use the system's trusted ca certificates to verify the EAP server. + Examples of Options ------------------- @@ -603,6 +655,23 @@ network_connections: rule_append_only: yes ``` +Configuring 802.1x: + +```yaml +network_connections: + - name: eth0 + type: ethernet + 802.1x: + identity: myhost + eap: tls + private-key: /etc/pki/tls/client.key + # recommend vault encrypting the private key password + # see https://docs.ansible.com/ansible/latest/user_guide/vault.html + private-key-password: "p@55w0rD" + client-cert: /etc/pki/tls/client.pem + ca-cert: /etc/pki/tls/cacert.pem +``` + ### Invalid and Wrong Configuration The `network` role rejects invalid configurations. It is recommended to test the role diff --git a/defaults/main.yml b/defaults/main.yml index c95b237..1d25d5e 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -20,16 +20,23 @@ network_provider_current: "{{ # Default to the auto-detected value network_provider: "{{ network_provider_current }}" +# wpa_supplicant is required if any 802.1x connections are defined +wpa_supplicant_required: "{{ network_connections | + json_query('[*][\"802.1x\"]') | flatten | count > 0 }}" +_network_packages_default_802_1x: ["{% if wpa_supplicant_required + %}wpa_supplicant{% endif %}"] + # The python-gobject-base package depends on the python version and # distribution: # - python-gobject-base on RHEL7 (no python2-gobject-base :-/) # - python3-gobject-base on Fedora 28+ +_network_packages_default_gobject_packages: ["python{{ + ansible_python['version']['major'] | replace('2', '')}}-gobject-base"] + network_service_name_default_nm: NetworkManager -network_packages_default_nm: - - ethtool - - NetworkManager - - "python{{ ansible_python['version']['major'] | replace('2', '') - }}-gobject-base" +network_packages_default_nm: "{{ ['ethtool', 'NetworkManager'] + + _network_packages_default_gobject_packages|select()|list() + + _network_packages_default_802_1x|select()|list()}}" network_service_name_default_initscripts: network diff --git a/examples/eth-with-802-1x.yml b/examples/eth-with-802-1x.yml new file mode 100644 index 0000000..4b5de25 --- /dev/null +++ b/examples/eth-with-802-1x.yml @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- hosts: network-test + vars: + network_connections: + - name: eth0 + type: ethernet + 802.1x: + identity: myhost + eap: tls + private-key: /etc/pki/tls/client.key + # recommend vault encrypting the private key password + # see https://docs.ansible.com/ansible/latest/user_guide/vault.html + private-key-password: "p@55w0rD" + client-cert: /etc/pki/tls/client.pem + ca-cert: /etc/pki/tls/cacert.pem + + # certs have to be deployed first + pre_tasks: + - name: copy certs/keys for 802.1x auth + copy: + src: "{{ item }}" + dest: "/etc/pki/tls/{{ item }}" + with_items: + - client.key + - client.pem + - cacert.pem + roles: + - linux-system-roles.network diff --git a/library/network_connections.py b/library/network_connections.py index 0ff7a04..2c46a40 100644 --- a/library/network_connections.py +++ b/library/network_connections.py @@ -974,6 +974,51 @@ class NMUtil: else: s_ip6.add_route(rr) + if connection["802.1x"]: + s_8021x = self.connection_ensure_setting(con, NM.Setting8021x) + + s_8021x.set_property(NM.SETTING_802_1X_EAP, [connection["802.1x"]["eap"]]) + s_8021x.set_property( + NM.SETTING_802_1X_IDENTITY, connection["802.1x"]["identity"] + ) + + s_8021x.set_property( + NM.SETTING_802_1X_PRIVATE_KEY, + Util.path_to_glib_bytes(connection["802.1x"]["private-key"]), + ) + + if connection["802.1x"]["private-key-password"]: + s_8021x.set_property( + NM.SETTING_802_1X_PRIVATE_KEY_PASSWORD, + connection["802.1x"]["private-key-password"], + ) + + if connection["802.1x"]["private-key-password-flags"]: + s_8021x.set_secret_flags( + NM.SETTING_802_1X_PRIVATE_KEY_PASSWORD, + Util.NM().SettingSecretFlags( + Util.convert_passwd_flags_nm( + connection["802.1x"]["private-key-password-flags"] + ), + ), + ) + + s_8021x.set_property( + NM.SETTING_802_1X_CLIENT_CERT, + Util.path_to_glib_bytes(connection["802.1x"]["client-cert"]), + ) + + if connection["802.1x"]["ca-cert"]: + s_8021x.set_property( + NM.SETTING_802_1X_CA_CERT, + Util.path_to_glib_bytes(connection["802.1x"]["ca-cert"]), + ) + + s_8021x.set_property( + NM.SETTING_802_1X_SYSTEM_CA_CERTS, + connection["802.1x"]["system-ca-certs"], + ) + try: con.normalize() except Exception as e: diff --git a/module_utils/network_lsr/argument_validator.py b/module_utils/network_lsr/argument_validator.py index b58ec67..6f0ca88 100644 --- a/module_utils/network_lsr/argument_validator.py +++ b/module_utils/network_lsr/argument_validator.py @@ -2,6 +2,7 @@ # vim: fileencoding=utf8 # SPDX-License-Identifier: BSD-3-Clause +import posixpath import socket # pylint: disable=import-error, no-name-in-module @@ -698,6 +699,59 @@ class ArgValidator_DictMacvlan(ArgValidatorDict): return result +class ArgValidatorPath(ArgValidatorStr): + """ + Valides that the value is a valid posix absolute path + """ + + def __init__(self, name, required=False, default_value=None): + ArgValidatorStr.__init__(self, name, required, default_value, None) + + def _validate_impl(self, value, name): + ArgValidatorStr._validate_impl(self, value, name) + + if posixpath.isabs(value) is False: + raise ValidationError( + name, "value '%s' is not a valid posix absolute path" % (value), + ) + return value + + +class ArgValidator_Dict802_1X(ArgValidatorDict): + + VALID_EAP_TYPES = ["tls"] + + VALID_PRIVATE_KEY_FLAGS = ["none", "agent-owned", "not-saved", "not-required"] + + def __init__(self): + ArgValidatorDict.__init__( + self, + name="802.1x", + nested=[ + ArgValidatorStr( + "eap", + enum_values=ArgValidator_Dict802_1X.VALID_EAP_TYPES, + default_value="tls", + ), + ArgValidatorStr("identity", required=True), + ArgValidatorPath("private-key", required=True), + ArgValidatorStr("private-key-password"), + ArgValidatorList( + "private-key-password-flags", + nested=ArgValidatorStr( + "private-key-password-flags[?]", + enum_values=ArgValidator_Dict802_1X.VALID_PRIVATE_KEY_FLAGS, + ), + default_value=None, + ), + ArgValidatorPath("client-cert", required=True), + ArgValidatorPath("ca-cert"), + ArgValidatorBool("system-ca-certs", default_value=False), + ], + default_value=None, + ) + + class ArgValidator_DictConnection(ArgValidatorDict): VALID_PERSISTENT_STATES = ["absent", "present"] @@ -759,6 +813,7 @@ class ArgValidator_DictConnection(ArgValidatorDict): ArgValidator_DictInfiniband(), ArgValidator_DictVlan(), ArgValidator_DictMacvlan(), + ArgValidator_Dict802_1X(), # deprecated options: ArgValidatorStr( "infiniband_transport_mode", @@ -1208,3 +1263,18 @@ class ArgValidator_ListConnections(ArgValidatorList): "profile references a master '%s' which has 'interface_name' " "missing" % (connection["master"]), ) + + # check if 802.1x connection is valid + if connection["802.1x"]: + if mode == self.VALIDATE_ONE_MODE_INITSCRIPTS: + raise ValidationError.from_connection( + idx, + "802.1x authentication is not supported by initscripts. " + "Configure 802.1x in /etc/wpa_supplicant.conf " + "if you need to use initscripts.", + ) + + if connection["type"] != "ethernet": + raise ValidationError.from_connection( + idx, "802.1x settings only allowed for ethernet interfaces." + ) diff --git a/module_utils/network_lsr/utils.py b/module_utils/network_lsr/utils.py index bd1887d..9e4b8a2 100644 --- a/module_utils/network_lsr/utils.py +++ b/module_utils/network_lsr/utils.py @@ -39,6 +39,33 @@ class Util: raise MyError("failure calling %s: exit with %s" % (argv, p.returncode)) return out + @staticmethod + def path_to_glib_bytes(path): + """ + Converts a path to a GLib.Bytes object that can be accepted by NM + """ + return Util.GLib().Bytes.new(("file://%s\x00" % path).encode("utf-8")) + + @staticmethod + def convert_passwd_flags_nm(secret_flags): + """ + Converts an array of "secret flags" strings + to an integer represantion understood by NetworkManager + """ + + flag_int = 0 + + if "none" in secret_flags: + flag_int += 0 + if "agent-owned" in secret_flags: + flag_int += 1 + if "not-saved" in secret_flags: + flag_int += 2 + if "not-required" in secret_flags: + flag_int += 4 + + return flag_int + @classmethod def create_uuid(cls): return str(uuid.uuid4()) diff --git a/tasks/main.yml b/tasks/main.yml index f7f041f..94c7680 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -32,6 +32,17 @@ when: - network_provider == "nm" +# If any 802.1x connections are used, the wpa_supplicant +# service is required to be running +- name: Enable and start wpa_supplicant + service: + name: wpa_supplicant + state: started + enabled: true + when: + - network_provider == "nm" + - wpa_supplicant_required + - name: Enable network service service: name: "{{ network_service_name }}" diff --git a/tests/ensure_non_running_provider.py b/tests/ensure_non_running_provider.py index ff61c5b..c5f30c4 100755 --- a/tests/ensure_non_running_provider.py +++ b/tests/ensure_non_running_provider.py @@ -24,6 +24,7 @@ IGNORE = [ "tests_default_nm.yml", "tests_default_initscripts.yml", "tests_default.yml", + "tests_802_1x_nm.yml", ] OTHER_PLAYBOOK = """ diff --git a/tests/files/cacert.pem b/tests/files/cacert.pem new file mode 100644 index 0000000..3b43a67 --- /dev/null +++ b/tests/files/cacert.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIUK0vXQP5fEgyCtfY4pt2yU9bDMlAwDQYJKoZIhvcNAQEL +BQAwVDELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE +CgwTRGVmYXVsdCBDb21wYW55IEx0ZDEQMA4GA1UEAwwHVGVzdCBDQTAeFw0yMDAy +MjgwODUxMzdaFw00MDAyMjMwODUxMzdaMFQxCzAJBgNVBAYTAlhYMRUwEwYDVQQH +DAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQxEDAO +BgNVBAMMB1Rlc3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDa +kLkNqQmEWCs/o39iPSlHrnuXvvqQUziGO635w16OeGgix4QY+H7My0uY801/WN2F +8dzHbpKkFJ3pvFhnbq/vcoUvY/Folb9YoMyjwEJoUiSyZ2n9YiV4HkpC+Y+aFG2P +IlBcGnRxBV4M35ZNpbvOwSWqgwIgN5Z7ET4nP0P1v5BzQa3FJYdd1nh2YEljGRE+ +aTchVhdIYN4+4YXx1rvzQs4I0Tqr3tGM4zCWTQp34LYnyQMAysXQOnwJsX8/G4Lw +AtH8cFexdoHft8uL8YHbxV8gDVBpvZSl9kmkX204hozJzsRQzSbuVw6wb2ggaYei +HdOwORYJfoC6eKOiYJEHczHkkchmbj8qFP1IKUQOPE39W8swO/2j56Qvfv9vBIUw +4QklSU3CLYpr1jjeHuKuOkSTlFSOyvN6HOxLla/YqdshGwdo8duKz3gr85lJbzHT +9IfI2r7g8fTwcVcH0DfS7Ku4OXTIqyJw/ZO77hMpF0x6U6GCHxi8I9sGjN0JVCeE +43/ad3tr9KOlGQuiDR9rz5XkCbdPgaiJ+P3nfO2fMFCUaFOr0lFj4meCKC49+sdw +CDDpW0Cqkm142co1/J64YNw/z2A3ESJrckP9NCOb6zkCZS6j+T2Nir0fI6Ix1uOX +SHKinD4KT0WgOrJLxlZdLxsCnHp57G9PQqK8RXU5VwIDAQABo1MwUTAdBgNVHQ4E +FgQUOJF6JV8Q3HPuOp1UZo+KyYcddgwwHwYDVR0jBBgwFoAUOJF6JV8Q3HPuOp1U +Zo+KyYcddgwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAUQ3f +e6EiWU/TBewakD/yrNzMxuqOS6ptDq6OPYolpdrkCogtUvL6wga7PVgEb8v+kBD1 +A0C4eA6Q+M/xOiFKSiErckQzEJggxN+C3N4rRS7F2fnMUjWjwApMxW/zPyTIGKDP +rxRNIJyfjuvxv8XnugDNjdg496beYlhOzvCVDO6hxt/V83NZBbcXY2GBUKnuldmd +21x7g/coajPuocWgK2s4OWU251ujCCggru5hRsxm3Ag68ZWCIqYu/rRN0z1V31OI +xc5OQsrkdrDez/iZMrw6N/rK8Xez0zqXU7v32gou7FoZxqx1yOZtkBMBsSXf/Fue +28maCNsE4S/HRcQQv91F/e/QtpfLmRBa6FIslBh334YzBKyuD6QLjMraDO3X8DjD +3i434Fgf5v7jttfEOrRKN7Yvb+IXZ25UhVGWUY1CsGQ57ZdUwwuD/bdeZLLAhl9X +r8PhHQ5YJV6NcGXMsvuvARlPILXLpAsP3IpXQq5l0GeHuRV2hwqiIV//qcgIz5qb +B7voqbr+2k652OOsG/tRIOvZlJBmdPkq+Beeutvx0j1VKNflBnxDMMO6zyhLVdO2 +RB8Lx2IEM8KA4p1IhnEi7g3mtQsu6IlY4Qfuje8rE0xMY28rxr977sBvvdsfkeUd +O/Ut71oKxQ/z0H/Iy1BiBoUzuK0XL079lou4ft0= +-----END CERTIFICATE----- diff --git a/tests/files/client.key b/tests/files/client.key new file mode 100644 index 0000000..061b192 --- /dev/null +++ b/tests/files/client.key @@ -0,0 +1,31 @@ +# password=test +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,C4A5E9A189773AB0F3CE3DCC98F208AE + +LPNSExpEERS+/qHJxd8puT+EaZ/dZ20gkU/C2eaNNJerzr4moSXG4ioh5ggz4utQ +w57fD5OYqPiloNIawi/Ta5Opo3zU+iMZPVQALLbemXWXmNMxqxNCGdonc4enxMoN +auLxpdYPW+infFmf0UPwZjWkrLnK8XFapTGDaNesfgMNSRVSt+DQL3xeKUjcuXfh +rYvF26/Ls8NHB0tCU449vCa5ta1fHPT78B0cWgCmhcg/L8/0veBYfwxnyu6l3E6Q +RXWcyaJoihhCSg9kCZOqQFKDtz3B9G8/G8P5n5udN2TYUK0ieCktocOip0r/aUfk +Rz/NPjej18tuvA9e+uho2DuEj7OV1Rt0Fr6G2NySDYAIjlzM1+GoDdX3R8Rva2eX +SJYEjQvvLMAXU9wLEGd2u9jw3h8g2rNPF34Mo/fZsU6f83WceN7wzaDjKBM9TC/U +DjeUpJ2LHr3SduRoq5K7PqTG6LlRx4ZC06P8Gwu/cjlHqHuMlLE6wWPHowp9O08S +zMzJji6csSzZ5x5U41xiBJd19G0tbfjGBOvxhVLC3hmfqMtRwgeKSZMUz5f0iFvS +V4LE/ZNXWv5OybEzMyIiQBRB0G8mq5BkQ3rU9uTMO6Xc6mosQy0jiCsQLYaX2IoT +kyU6ZqPgAeBD3g5zCGudcF4qqY3pWRU6cijpivsuyX58YmulhQJsB2rnoImv8ZOR +4Uw+fvAx38v/dH/aAGKNdQV/4z+CXpAX4SdqYgBx9wXu6Wva31AVrbDrKnpSlWYF +M9gAHgpuhW9OH7du/y7sePU6k37fHtqDX0V5XoyeRxixR+KGb8k3tt0HFA1GExSu +XyXcOOfwec7xNQjZBM9jREI0yO1tCbHEeLsLpQnf31cpfSQumBZoiim6Vyk7vCN8 +YBJ9qiVNrFiVogWl5hUrSS2MLQP1ZQBkedmOeKZpkZ26GW5yY0y27v2mHdhU2Dvd +otvLGiVKxSXlu+tqt1WkMvu6hcfrDZDCONW7emGW7xs2vdYdvADVlYs/Eb0WFXb1 +tLkwg3v7I23LeFRrKX4Fm5/biG4GuR4sj9iPLayrKWhpujIVFJqHTI3YhjIU56Qp +uPuClnoFsKrWS9DXaziuuXmLZlXH3e5aOO+M2H3JmXTRCojyjKlIJiJJmHGrfwfe +oJkSF+ABs2zrpteXU+Cnfn8V01TrtxPYIBF3CbOMZEvwgjPLX0UNtnss0hXH4rJe +9yF/PiKWehUow8q4Gpwt2PnLkUWyL21GwCwXf5Cq3yRAKtyrJTlJsdYV1f3brzfb +JkBgKaFJ44Ee7D75PAio8g/BIDpvUdZVXwn3FizjfAU+HhXonPSYb2M34C6I/frk +mJPgZ5hbpt1SoCCER48+rQygiLdNQH6OsuhJeEElPFYwNo6i5jZsZ9iE0rmJxGgk +m7Mhi491NdK8L6Kh8kM2Dgupsfcstmx4+pI3gmgnsYZApmFoQlfcg4MhbWqxznv+ +cPm1n2SZMoMLru44vbnjW+ZAggen5zNZOrsVt8UImSBVKfAIrgDUuYIv7uqUiKHI +yHmAkZDlqEbpkbUG9m60OeuEIgpN7MT3Kod387ZyOu9uaTZWdD18/N83E4eFecND +-----END RSA PRIVATE KEY----- diff --git a/tests/files/client.key.nocrypt b/tests/files/client.key.nocrypt new file mode 100644 index 0000000..b1e1dbf --- /dev/null +++ b/tests/files/client.key.nocrypt @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAuQipSk9+0rd/qBMDRiFzV8vDksaueVphejGEgiQhqtUDgjc/ +ot/o7M8fFVC6wau2ixTnEMHuZXgoBOKATxX805FggEsLLL98OnN7AyTTKOtHVfIm +gK3fJ1Y9l95+2nuJWhmaan0vr3YMp6z3lSa+hlhhTYx/mIvTZho/K3+METg8DEfl +QUSkhAlrFSEahr2Pu/yETr8c+8vKTDnZDLvcFyyuDtAz+clEQUVndWJQpQpSVfR9 +4xKuzaj10mUA9Utv4RkbNJ78/KgdTbaGIOVLUnYCJUg8d3/YV7aNCqraHBAZ9aoP +S4dl46KXC3qpaEBFfKaF+RcPSVUtc4eCQ74kKwIDAQABAoIBAGoArUN2IVjEaSy3 +n7OIrFSK1oL6sa+x+JARWDFaU7NTj0wFLL65ee5Yhh0m/6a+IbiyA+IUx+d3m62Y +uRsVpJ7r9RXqZ/99v8SYrctSSGpzx41USXyEn4ggnu6nN5MhHMHyUwVYrH3fqkZR +EBFxfcrnTO8pY1vYFwayWKgpzOt7ip30JF1E7RH0IWfA2koJ+hZgSumPmF31btBK +eqDaQ168u0at6I7nYvRIWVT68D2k+PMb/c/rlOUYSyy+VfCgnShWD+m1hlyaDF1c +cbVvOhsul3rFeEqbToGN/6yyDDcyolTvYxMm3vb6jmoExZyRsShv0XyhokSuCN9P +v5SeNpkCgYEA7OpIlsZUoTXm2ffCQiZd8gRtKk0O3dzmWTkcNEgj2uUNH6ANNy3W +gLojKeF2EyC3appRWLVRYN/m6r/Qj+rztZfW3Jw1UJQV+tLEOBzk3yBnRdh1aRgW +8YTH1+HJqlJ/2iKJRKRhseM5AHiTslp7ude6cWQxO52pJ6Rbp1z3fBUCgYEAx/B4 +LreIDJYDnYSyL/CvVkHEn1hCYX0oBpefzV6ofYDqv0OLe8BWOBsShQ3Crh0FuQTa +xV2xc+OzDewlu2OwNm4/X0qjXvoWkEMLBXKEHjPyxnbHLCYaaA/9ENmVIkc8aZWE +p7KcCYGlfiHpbdYWAD8KYdv5CsFHFbwhPwrD7z8CgYAEtsSq+1dDvebR/3QGDO1h +m2TwqofZMkQDEnfVMnpEKLqSHoUky+ywswNwGeRXjRcZL+jecv0jiFD36skjk/E1 +c8f6q8ED0W5+hyMQWsLTDboAUcZESQ5rz9CKIxv4H5wbowRIMV0gRP0lXUDTE6nS +kNBM4Ul5fjGXcFXChr8F4QKBgGSmAeoKi9tCHTnLVePaNnmmi/Nm+6uV1HNVGqXI +k+rx3bpAp1O5o+2Ee1MtdSYvB/V2oyadnrnnEvjcOrZVXZxY7V/r88fY/0jJ5x9r +4WRO5FTR8DuiRsLB4bP8xB1IXPoNwYSl3fTPJd8T9S1MizC+i1xt3rVyTHV9igLx +SWcDAoGBAMoynJvQUOssWwFTtNQK0ptz95rrTkO2bri+8MJfSh8tessekwPHVe6M +SBofFhDiesrHBHczJ61qDnb3GemA0kEbo023mxNo0HPam+OFgX5mrihizBZnRZjh +aecVouDd0uwacsB76fwP6Fl5GhkFvOSBKr2IKNJjUMXyvW8/XGZE +-----END RSA PRIVATE KEY----- diff --git a/tests/files/client.pem b/tests/files/client.pem new file mode 100644 index 0000000..ce96d9c --- /dev/null +++ b/tests/files/client.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqDCCApCgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwVDELMAkGA1UEBhMCWFgx +FTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55 +IEx0ZDEQMA4GA1UEAwwHVGVzdCBDQTAeFw0yMDAyMjgwOTQ0MjlaFw00NzA3MTUw +OTQ0MjlaMF0xCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAa +BgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQxGTAXBgNVBAMMEFRlc3QgQ2xpZW50 +IENlcnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5CKlKT37St3+o +EwNGIXNXy8OSxq55WmF6MYSCJCGq1QOCNz+i3+jszx8VULrBq7aLFOcQwe5leCgE +4oBPFfzTkWCASwssv3w6c3sDJNMo60dV8iaArd8nVj2X3n7ae4laGZpqfS+vdgyn +rPeVJr6GWGFNjH+Yi9NmGj8rf4wRODwMR+VBRKSECWsVIRqGvY+7/IROvxz7y8pM +OdkMu9wXLK4O0DP5yURBRWd1YlClClJV9H3jEq7NqPXSZQD1S2/hGRs0nvz8qB1N +toYg5UtSdgIlSDx3f9hXto0KqtocEBn1qg9Lh2XjopcLeqloQEV8poX5Fw9JVS1z +h4JDviQrAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5T +U0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBShQJXhPekXBpAdhKUK +vwip1379NzAfBgNVHSMEGDAWgBQ4kXolXxDcc+46nVRmj4rJhx12DDANBgkqhkiG +9w0BAQsFAAOCAgEAzY1ifNUYYVvWt0vR+gq37rLG5QZvlt5/pkVDYHgolChZCDmD +NWTH5MR/T+L+akFzbuIeOfQzTQMW/8fm9lHf6/e2coDtzLDKkukVCKIGZgrkyF9N +wT1xnvqCAERw0WfGFJn7auhIrExqgsrJbAorUMnVZAdrBosDWGMULNquyZMfVvAE +VXDDqjQIXe3AMA2yOgMEWoBVQMFl33BTz/NlGaE15wR8OsYRAs8/8nJewCLg4f6a +n2wz1/JIZI0ztTCO1cMrmI8i7TYIUlHQMK1QhF0aEFV1yFj+QDEc0iiIZu+i6O45 +K1TjElXIr4MrEzdI3A5ZIN+VZ7mtzq/2CIZTTir94XLcQ3BvwYJssMWxAPQb6lRA +4k+7xifr3V7u7glMxpilIxV5CpZZ4anqRMzFbJa2MOmyAXZxk7SpcD2/MugHfJiL +TWfwFcClRogqXx/loEgzXExV1MrYKQBrqMlHB5eZ77M+PFpMhVh7yY5jjWT0giOM +MXFFKL8sI3VZdT9VcKZLgKpl4KWDh+cldyJdNNCDA29gtCRO+N+d4CAl6dK6nRrI +lL81UTEexmaCL9YjLBs70/HBKCkUDOmtAR7gJ01S4Kogv4gIepCAcGmVItzF+6fw +YsbkynxXIHQcODaB2d7FFzP0QJkc6pSMIi7ioO7a+aYx8NpIDYIxxLzF39Q= +-----END CERTIFICATE----- diff --git a/tests/files/dh.pem b/tests/files/dh.pem new file mode 100644 index 0000000..4dfaa5a --- /dev/null +++ b/tests/files/dh.pem @@ -0,0 +1,8 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEAjbYPkANn2XGqDGCzse9wAfM0I5WJpp+Xl+iNJFmaKXBguo0BPYQt +hZOpJbKL3aNaFsRxhdAJ8UXzBP6oIzCejcGti+jw+xtVk8ietWEK6e91yi+Ak2g2 +/Xtt9hoYQkeoe5hkcv35NcJ0xdQwlSvMbY/j8HtKamx/A3zu+YPQAe/3AOe3L+JT +iEL5Gw00NPVnyEWKX4fVchAbMUkRsQKeXtsyOyDc4/RccjfLa1toyj8PRommK5UH +dkSqi04FTOUIx6aTwt21EehJuggLVDShoQdxGV+FzXmdtelLmerGMtVPBbf8DSkN +MKMBEg4d28DzjXPAWUHMD+JGPzAlvf87EwIBAg== +-----END DH PARAMETERS----- diff --git a/tests/files/server.key b/tests/files/server.key new file mode 100644 index 0000000..872ea8f --- /dev/null +++ b/tests/files/server.key @@ -0,0 +1,31 @@ +# password=test +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,ED349A8B098E2D1DB70C30F77EF599AB + +j1rzje2sWFk3B9kD6eE7WrqVDynFEJ3t3kdOv0iUvH5Ybll1C7Qx3EFEdoM4z2OV +E6q3nr2DOvpMPox1DvBdIipWOQWJxkZyBHqNn4v4GR4c0uxLswsk7XSBQLUclRsn +QBGO6x8pcEA9u/O3PSrTt+pVozWrXWmR2UHNM//9WUsRpWF4Lv0EINzsfwmD7aJQ +nRcSfXsCggXP6wnJX5dgo5PlRm6R+bodgzePr0QRlh8TT6wnixZfWalYM5iUKlEF +GcE+VejZuBL69byl2AcRt8I5tQ+UZxmzhKPSsYN0NKD8vbcVVnp2sre/rbdTzWz5 +laF386g1M8QBimDE/V3Bw5b9Bg1ZP3arlpugVXGVNA+HFti8PVdkaMqLgkFIC2Xu +OwmNKffAPIItuB8leg5A76oLoIlllRqjWO9M/O+MqAlrJ96xLRiUeGkez4Pp7eFV +30YrlOXyzwZKfXoOPIfE5Mbz4CPqR67XuqW8jOryIGOryMB17b0+vdRpDY0wxk8/ +lGmc5rglDxLFA8dNemAHDednasCuVlrbsQsZRnPkKavXiSu7QCbvm1frAXZfnyRp +TpPmE6L4+nEy8PQnK/IxOCqRcy6e1SPezRpajRjB5ooDT8hDmDkG47NdnrB+kOKL +5LIpATLSGS9IVk0RW/M8EqJP1kRh2JOCQT3V+gUN0ttz8bjZpivKnp76/ztg0lo0 +oC2lhuXV5HOYHw1z5jDazsYpQDYoHgYWXnzPJJp6Ecn+nkjZMKQjDV9ZqE1miPrZ +E4V0ULNmWaAQHvwc98yR97ui1YHmw5XVMoeDhy3fhB6IOyaGGdEj9o2iQr8kp9GC +dxBKK/xMOU6kwDF9Nsfh46veRGTbhAJdGeWqdxscdCupkO8KRtZqzL454+9GnYfe +n1f7wxJh7aTLNjF2an5Qa9v7uU6D58+9blxG7ls5qGt4xjBNAXCc8bPpmLqeCW4G +Xz8iwxECvwWIQ+SjUcXuP8+/NO58B14kDNP03+1gA7AHIesa2CTvHLCyMPaN2oGK +3R4LNxQQDNygEzRj8vHjURU1FNRJ4RjCi7SbqoOsl31Hvef6j0lcW0Sz4UICcCJI +p4NPnApoaHewL4exvlJ80qPbFscuVevXBlUC2LdxXS+9E+c0NaLauEeNYCUoaBDi +HIpbxRKXmqLc4LAKYVuEcIBFhdXp3UC9niVd7Nrguu0lUJXC78OzpltxWrqX/u4E +O2aCNK0Yg9U+rxm6wyccqEyptIS2GRCIpUGD/LVF3mOC16NB/JeYGrOWvDptdCeg +9pJrakJjE1Fm3pg4Xc74bT6IDj0EKwKSvZhtlcsM9JaXWChe/ZrDPPI/NP6MuyW4 +jcqpa9HPBBSyaxKsEPXFJhdhrz8VfsU2e5VvcALaJaAOpHwZgaNUpvpsY4LPW9mi +lHsecEBiq6re0r7TAgBE1AnlaI4ho0fKSgSub3NWUZlEaBK3X2n/Li6op6LIsvM5 +iySYaAluQy4dANww0KhQHMIh0jbuZGzmG2Hxk/poorYRf60YJlbTnHVD/FKUdFX+ +rUow0iy8Ez1uF272u5orYW2tBbkhSaieKOT8f4HFCxUsgITbd8Lf/XJ6l6Qns6SK +-----END RSA PRIVATE KEY----- diff --git a/tests/files/server.pem b/tests/files/server.pem new file mode 100644 index 0000000..4e389c5 --- /dev/null +++ b/tests/files/server.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqDCCApCgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwVDELMAkGA1UEBhMCWFgx +FTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55 +IEx0ZDEQMA4GA1UEAwwHVGVzdCBDQTAeFw0yMDAyMjgwOTA2MTlaFw00NzA3MTUw +OTA2MTlaMF0xCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAa +BgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQxGTAXBgNVBAMMEFRlc3QgU2VydmVy +IENlcnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDFwLjgCZ0pAEPB +HrtoxM+qY27od+Np1mqwPbFmPlcxV2fiRquqxVghsow6r5JHOCblpn+TNAgKlckx +d+PXpwgPoELE15x8lU5+8/IjSQJRsx/VhmJEbYWYHZWwbwBUMZaafhrtKZfJoIDR +pzx2SKmeUIXad10uFwFqT8uz6GNFg3tVIsu/E7wpPjaK+G4/1iAZjUrli1p73Qfg +cGIrFueTA5TGL3ChsRFJdIuBvh765fxZGurEghiYcX4bO/mSKVEWs/AoGajeJ1U+ +uqOxyFl7Sjyb+ds2jdLNYYj191efT4qBmhB0bdmLFfq46GsKoneImaceSnhpcovl +jsmZf8UVAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5T +U0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTHtNcLFSKihvvmv/WQ +zKkJjL0yPTAfBgNVHSMEGDAWgBQ4kXolXxDcc+46nVRmj4rJhx12DDANBgkqhkiG +9w0BAQsFAAOCAgEALDrMv9TBWjVMeBUaRiaF+CDVD+Ng1Frzp37tSLbBmfcViaPl +2vQ23wDTc0SU+bamfyrIF0IRDDNM6cUkuC306+18AtXcz1taPFu6mWhtz+j4Hncm +EpMSUxvmTvr9dSUuEdX74v7tA4YqThzolEbsedAr8oe6RBjntAKugG9035z/mWuO +WNw8P3QI6eSX/APzFON/9dt9fkKU2piKgDQYvNEtCPqp1Tr2MkdRzfvyL/ZGR5yn +kuSRZLSFJyBHnnjCjrK40nt0ooVFXTpqDb8gEFn3DOLXHWLy6tWAQi6fKBQWai7v +8Mz7OyQHEwEsdM3bLWVJwe4tzJA2Ct22HsHWixuEEtQFwnuGm/tEOJ3HwVPjn5QP +2ut8yH5Ij45XeBTxkMiKie/Eb43ob8o30jCUtGuM48azyZ+byaIqVYZ/DX8NQAdC +EGRA1nWHmr9nFvcpa98kjiKcQtb7Nb/Ewq5ys/mYAwAs3yD9FNt+ujb1/y9PLN5Q ++NkcFrPuCmIj5c3jOi0AwPD+WuTHBwJ3D7+2gUByVYf6GravI6N6uXEjOD2/Wcl0 +TtjlhiWvMK9bXu2F4FBLc+GXrawiG4aTNmi278bTSF6qIxrN5+A5JRU7eUtbCnpY +piA3Y/2Pu8YoM3coGWqtVfFddJr1raQ5LHjxIfDUd0kKPbfpjlaaa3aJQdg= +-----END CERTIFICATE----- diff --git a/tests/playbooks/files b/tests/playbooks/files new file mode 120000 index 0000000..feb1228 --- /dev/null +++ b/tests/playbooks/files @@ -0,0 +1 @@ +../files \ No newline at end of file diff --git a/tests/playbooks/tests_802_1x.yml b/tests/playbooks/tests_802_1x.yml new file mode 100644 index 0000000..c9d51ae --- /dev/null +++ b/tests/playbooks/tests_802_1x.yml @@ -0,0 +1,106 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- hosts: all + vars: + interface: 802-1x-test + tasks: + - name: "INIT: 802.1x tests" + debug: + msg: "##################################################" + - include_tasks: tasks/setup-802_1x_server.yml + - name: Copy client certs + copy: + src: "{{ item }}" + dest: "/etc/pki/tls/{{ item }}" + mode: 0644 + with_items: + - client.key + - client.key.nocrypt + - client.pem + - cacert.pem + - block: + - name: "TEST: 802.1x profile with private key password and ca cert" + debug: + msg: "##################################################" + - import_role: + name: linux-system-roles.network + vars: + network_connections: + - name: "{{ interface }}" + interface_name: veth2 + state: up + type: ethernet + ip: + address: + - 203.0.113.2/24 + dhcp4: "no" + auto6: "no" + 802.1x: + identity: myhost + eap: tls + private-key: /etc/pki/tls/client.key + private-key-password: test + private-key-password-flags: + - none + client-cert: /etc/pki/tls/client.pem + ca-cert: /etc/pki/tls/cacert.pem + - name: "TEST: I can ping the EAP server" + shell: ping -c1 203.0.113.1 + - import_role: + name: linux-system-roles.network + vars: + network_connections: + - name: "{{ interface }}" + persistent_state: absent + state: absent + - name: >- + TEST: 802.1x profile with unencrypted private key and + system ca certs + debug: + msg: "##################################################" + - name: Copy cacert to system truststore + copy: + src: cacert.pem + dest: /etc/pki/ca-trust/source/anchors/cacert.pem + mode: 0644 + - name: Update ca trust + shell: update-ca-trust + - import_role: + name: linux-system-roles.network + vars: + network_connections: + - name: "{{ interface }}" + interface_name: veth2 + state: up + type: ethernet + ip: + address: + - 203.0.113.2/24 + dhcp4: "no" + auto6: "no" + 802.1x: + identity: myhost + eap: tls + private-key: /etc/pki/tls/client.key.nocrypt + client-cert: /etc/pki/tls/client.pem + private-key-password-flags: + - not-required + system-ca-certs: True + - name: "TEST: I can ping the EAP server" + shell: ping -c1 203.0.113.1 + always: + - block: + - import_role: + name: linux-system-roles.network + vars: + network_connections: + - name: "{{ interface }}" + persistent_state: absent + state: absent + - name: br1 + persistent_state: absent + state: absent + ignore_errors: true + - include_tasks: tasks/cleanup-802_1x_server.yml + tags: + - "tests::cleanup" diff --git a/tests/tasks/cleanup-802_1x_server.yml b/tests/tasks/cleanup-802_1x_server.yml new file mode 100644 index 0000000..5d97932 --- /dev/null +++ b/tests/tasks/cleanup-802_1x_server.yml @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- name: Remove test interfaces + shell: | + ip netns delete ns1 + ip link delete veth1-br + ip link delete veth2-br + ip link delete br1 + +- name: Kill hostapd process + shell: pkill hostapd diff --git a/tests/tasks/setup-802_1x_server.yml b/tests/tasks/setup-802_1x_server.yml new file mode 100644 index 0000000..1b9cffc --- /dev/null +++ b/tests/tasks/setup-802_1x_server.yml @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- name: Install EPEL on enterprise Linux for hostapd + # yamllint disable-line rule:line-length + command: yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_distribution_major_version }}.noarch.rpm + args: + warn: false + creates: /etc/yum.repos.d/epel.repo + when: + - ansible_distribution in ['RedHat', 'CentOS'] + +- name: Install hostapd + package: + name: hostapd + state: present + +- name: Copy server certificates + copy: + src: "{{ item }}" + dest: "/etc/pki/tls/{{ item }}" + with_items: + - server.key + - dh.pem + - server.pem + - cacert.pem + +- name: Create test interfaces + shell: | + ip link add veth1 type veth peer name veth1-br + ip link add veth2 type veth peer name veth2-br + + ip link add br1 type bridge + ip link set br1 up + + ip netns add ns1 + + ip link set veth1 netns ns1 + + ip netns exec ns1 ip addr add 203.0.113.1/24 dev veth1 + + ip link set veth1-br up + ip link set veth2-br up + + ip link set veth1-br master br1 + ip link set veth2-br master br1 + + ip netns exec ns1 ip link set veth1 up + ip link set veth2 up + + # Enable forwarding of EAP 802.1x messages through software bridge "br1". + echo 8 > /sys/class/net/br1/bridge/group_fwd_mask + +- name: Create hostapd config + copy: + content: | + interface=veth1 + driver=wired + debug=2 + ieee8021x=1 + eap_reauth_period=3600 + eap_server=1 + use_pae_group_addr=1 + eap_user_file=/etc/hostapd/hostapd.eap_user + ca_cert=/etc/pki/tls/cacert.pem + dh_file=/etc/pki/tls/dh.pem + server_cert=/etc/pki/tls/server.pem + private_key=/etc/pki/tls/server.key + private_key_passwd=test + logger_syslog=-1 + logger_syslog_level=0 + dest: /etc/hostapd/wired.conf + +- name: Create eap_user_file config + copy: + content: | + * TLS + dest: /etc/hostapd/hostapd.eap_user + +- name: Run hostapd in namespace + shell: ip netns exec ns1 hostapd -B /etc/hostapd/wired.conf && sleep 5 diff --git a/tests/tests_802_1x_nm.yml b/tests/tests_802_1x_nm.yml new file mode 100644 index 0000000..f4ad1be --- /dev/null +++ b/tests/tests_802_1x_nm.yml @@ -0,0 +1,15 @@ +--- +# set network provider and gather facts +- hosts: all + tasks: + - name: Set network provider to 'nm' + set_fact: + network_provider: nm + +# workaround for: https://github.com/ansible/ansible/issues/27973 +# There is no way in Ansible to abort a playbook hosts with specific OS +# releases Therefore we include the playbook with the tests only if the hosts +# would support it. +# The test requires NetworkManager, therefore it cannot run on RHEL/CentOS 6 +- import_playbook: playbooks/tests_802_1x.yml + when: ansible_distribution_major_version != '6' diff --git a/tests/unit/test_network_connections.py b/tests/unit/test_network_connections.py index 9c2f0ed..84e8b56 100755 --- a/tests/unit/test_network_connections.py +++ b/tests/unit/test_network_connections.py @@ -66,6 +66,7 @@ def pprint(msg, obj): ARGS_CONNECTIONS = network_lsr.argument_validator.ArgValidator_ListConnections() VALIDATE_ONE_MODE_INITSCRIPTS = ARGS_CONNECTIONS.VALIDATE_ONE_MODE_INITSCRIPTS +VALIDATE_ONE_MODE_NM = ARGS_CONNECTIONS.VALIDATE_ONE_MODE_NM ETHTOOL_FEATURES_DEFAULTS = { "esp-hw-offload": None, @@ -153,6 +154,7 @@ class TestValidator(unittest.TestCase): }, "mac": None, "master": None, + "802.1x": None, "mtu": None, "name": "5", "parent": None, @@ -197,7 +199,8 @@ class TestValidator(unittest.TestCase): if "type" in connection: connection["nm.exists"] = False connection["nm.uuid"] = n.Util.create_uuid() - mode = VALIDATE_ONE_MODE_INITSCRIPTS + + mode = VALIDATE_ONE_MODE_NM for idx, connection in enumerate(connections): try: ARGS_CONNECTIONS.validate_connection_one(mode, connections, idx) @@ -248,7 +251,7 @@ class TestValidator(unittest.TestCase): continue if "type" not in connection: continue - if connection["type"] in ["macvlan"]: + if connection["type"] in ["macvlan"] or connection["802.1x"]: # initscripts do not support this type. Skip the test. continue content_current = kwargs.get("initscripts_content_current", None) @@ -394,6 +397,7 @@ class TestValidator(unittest.TestCase): }, "mac": None, "master": None, + "802.1x": None, "mtu": None, "name": "5", "parent": None, @@ -444,6 +448,7 @@ class TestValidator(unittest.TestCase): }, "mac": None, "master": None, + "802.1x": None, "mtu": None, "name": "5", "parent": None, @@ -488,6 +493,7 @@ class TestValidator(unittest.TestCase): }, "mac": None, "master": None, + "802.1x": None, "mtu": None, "name": "5", "parent": None, @@ -575,6 +581,7 @@ class TestValidator(unittest.TestCase): }, "mac": "52:54:00:44:9f:ba", "master": None, + "802.1x": None, "mtu": 1450, "name": "prod1", "parent": None, @@ -636,6 +643,7 @@ class TestValidator(unittest.TestCase): }, "mac": None, "master": None, + "802.1x": None, "mtu": None, "name": "prod1", "parent": None, @@ -699,6 +707,7 @@ class TestValidator(unittest.TestCase): }, "mac": "52:54:00:44:9f:ba", "master": None, + "802.1x": None, "mtu": 1450, "name": "prod1", "parent": None, @@ -754,6 +763,7 @@ class TestValidator(unittest.TestCase): }, "mac": None, "master": None, + "802.1x": None, "mtu": None, "name": "prod.100", "parent": "prod1", @@ -836,6 +846,7 @@ class TestValidator(unittest.TestCase): }, "mac": "52:54:00:44:9f:ba", "master": None, + "802.1x": None, "mtu": 1450, "name": "prod1", "parent": None, @@ -891,6 +902,7 @@ class TestValidator(unittest.TestCase): }, "mac": None, "master": None, + "802.1x": None, "mtu": None, "name": "prod.100", "parent": "prod1", @@ -968,6 +980,7 @@ class TestValidator(unittest.TestCase): }, "mac": "33:24:10:24:2f:b9", "master": None, + "802.1x": None, "mtu": 1450, "name": "eth0-parent", "parent": None, @@ -1018,6 +1031,7 @@ class TestValidator(unittest.TestCase): "mac": None, "macvlan": {"mode": "bridge", "promiscuous": True, "tap": False}, "master": None, + "802.1x": None, "mtu": None, "name": "veth0.0", "parent": "eth0-parent", @@ -1068,6 +1082,7 @@ class TestValidator(unittest.TestCase): "mac": None, "macvlan": {"mode": "passthru", "promiscuous": False, "tap": True}, "master": None, + "802.1x": None, "mtu": None, "name": "veth0.1", "parent": "eth0-parent", @@ -1153,6 +1168,7 @@ class TestValidator(unittest.TestCase): }, "mac": None, "master": None, + "802.1x": None, "mtu": None, "name": "prod2", "parent": None, @@ -1189,6 +1205,7 @@ class TestValidator(unittest.TestCase): }, "mac": None, "master": "prod2", + "802.1x": None, "mtu": None, "name": "prod2-slave1", "parent": None, @@ -1249,6 +1266,7 @@ class TestValidator(unittest.TestCase): }, "mac": None, "master": None, + "802.1x": None, "mtu": None, "name": "bond1", "parent": None, @@ -1294,6 +1312,7 @@ class TestValidator(unittest.TestCase): }, "mac": None, "master": None, + "802.1x": None, "mtu": None, "name": "bond1", "parent": None, @@ -1349,6 +1368,7 @@ class TestValidator(unittest.TestCase): }, "mac": "aa:bb:cc:dd:ee:ff", "master": None, + "802.1x": None, "mtu": None, "name": "5", "parent": None, @@ -1392,6 +1412,7 @@ class TestValidator(unittest.TestCase): }, "mac": None, "master": None, + "802.1x": None, "mtu": None, "name": "5", "parent": None, @@ -1463,6 +1484,7 @@ class TestValidator(unittest.TestCase): }, "mac": None, "master": None, + "802.1x": None, "mtu": None, "name": "6643-master", "parent": None, @@ -1499,6 +1521,7 @@ class TestValidator(unittest.TestCase): }, "mac": None, "master": "6643-master", + "802.1x": None, "mtu": None, "name": "6643", "parent": None, @@ -1551,6 +1574,7 @@ class TestValidator(unittest.TestCase): }, "mac": None, "master": None, + "802.1x": None, "mtu": None, "name": "infiniband.1", "parent": None, @@ -1621,6 +1645,7 @@ class TestValidator(unittest.TestCase): "mac": "11:22:33:44:55:66:77:88:99:00:" "11:22:33:44:55:66:77:88:99:00", "master": None, + "802.1x": None, "mtu": None, "name": "infiniband.2", "parent": None, @@ -1711,6 +1736,7 @@ class TestValidator(unittest.TestCase): }, "mac": None, "master": None, + "802.1x": None, "mtu": None, "name": "555", "parent": None, @@ -1809,6 +1835,7 @@ class TestValidator(unittest.TestCase): }, "mac": None, "master": None, + "802.1x": None, "mtu": None, "name": "e556", "parent": None, @@ -1887,6 +1914,255 @@ class TestValidator(unittest.TestCase): ], ) + def test_802_1x_1(self): + """ + Test private key with password + """ + self.maxDiff = None + self.do_connections_validate( + [ + { + "actions": ["present", "up"], + "autoconnect": True, + "check_iface_exists": True, + "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, + "force_state_change": None, + "ignore_errors": None, + "interface_name": "802.1x-1", + "ip": { + "gateway6": None, + "gateway4": None, + "route_metric4": None, + "auto6": True, + "dhcp4": True, + "address": [], + "route_append_only": False, + "rule_append_only": False, + "route": [], + "dns": [], + "dns_search": [], + "route_metric6": None, + "dhcp4_send_hostname": None, + }, + "mac": None, + "master": None, + "802.1x": { + "identity": "myhost", + "eap": "tls", + "private-key": "/etc/pki/tls/client.key", + "private-key-password": "p@55w0rD", + "private-key-password-flags": None, + "client-cert": "/etc/pki/tls/client.pem", + "ca-cert": "/etc/pki/tls/cacert.pem", + "system-ca-certs": False, + }, + "mtu": None, + "name": "802.1x-1", + "parent": None, + "persistent_state": "present", + "slave_type": None, + "state": "up", + "type": "ethernet", + "wait": None, + "zone": None, + } + ], + [ + { + "name": "802.1x-1", + "state": "up", + "type": "ethernet", + "802.1x": { + "identity": "myhost", + "eap": "tls", + "private-key": "/etc/pki/tls/client.key", + "private-key-password": "p@55w0rD", + "client-cert": "/etc/pki/tls/client.pem", + "ca-cert": "/etc/pki/tls/cacert.pem", + }, + } + ], + ) + + def test_802_1x_2(self): + """ + Test private key without password and system-ca-certs + """ + self.maxDiff = None + self.do_connections_validate( + [ + { + "actions": ["present", "up"], + "autoconnect": True, + "check_iface_exists": True, + "ethernet": ETHERNET_DEFAULTS, + "ethtool": ETHTOOL_DEFAULTS, + "force_state_change": None, + "ignore_errors": None, + "interface_name": "802.1x-2", + "ip": { + "gateway6": None, + "gateway4": None, + "route_metric4": None, + "auto6": True, + "dhcp4": True, + "address": [], + "route_append_only": False, + "rule_append_only": False, + "route": [], + "dns": [], + "dns_search": [], + "route_metric6": None, + "dhcp4_send_hostname": None, + }, + "mac": None, + "master": None, + "802.1x": { + "identity": "myhost", + "eap": "tls", + "private-key": "/etc/pki/tls/client.key", + "private-key-password": None, + "private-key-password-flags": ["not-required"], + "client-cert": "/etc/pki/tls/client.pem", + "ca-cert": None, + "system-ca-certs": True, + }, + "mtu": None, + "name": "802.1x-2", + "parent": None, + "persistent_state": "present", + "slave_type": None, + "state": "up", + "type": "ethernet", + "wait": None, + "zone": None, + } + ], + [ + { + "name": "802.1x-2", + "state": "up", + "type": "ethernet", + "802.1x": { + "identity": "myhost", + "eap": "tls", + "private-key": "/etc/pki/tls/client.key", + "client-cert": "/etc/pki/tls/client.pem", + "private-key-password-flags": ["not-required"], + "system-ca-certs": True, + }, + } + ], + ) + + def test_invalid_cert_path(self): + """ + should fail if a relative path is used for 802.1x certs/keys + """ + self.maxDiff = None + self.do_connections_check_invalid( + [ + { + "name": "802.1x-bad", + "state": "up", + "type": "ethernet", + "802.1x": { + "identity": "myhost", + "eap": "tls", + "private-key": "client.key", + "client-cert": "client.pem", + "private-key-password-flags": ["not-required"], + "system-ca-certs": True, + }, + } + ] + ) + + def test_invalid_password_flag(self): + """ + should fail if an invalid private key password flag is set + """ + self.maxDiff = None + self.do_connections_check_invalid( + [ + { + "name": "802.1x-bad", + "state": "up", + "type": "ethernet", + "802.1x": { + "identity": "myhost", + "eap": "tls", + "private-key": "/etc/pki/tls/client.key", + "client-cert": "/etc/pki/tls/client.pem", + "private-key-password-flags": ["bad-flag"], + "system-ca-certs": True, + }, + } + ] + ) + + def test_802_1x_initscripts(self): + """ + should fail to create 802.1x connection with initscripts + """ + input_connections = [ + { + "name": "802.1x-is", + "state": "up", + "type": "ethernet", + "802.1x": { + "identity": "myhost", + "eap": "tls", + "private-key": "/etc/pki/tls/client.key", + "client-cert": "/etc/pki/tls/client.pem", + "private-key-password-flags": ["not-required"], + "system-ca-certs": True, + }, + } + ] + + connections = ARGS_CONNECTIONS.validate(input_connections) + + self.assertRaises( + n.ValidationError, + ARGS_CONNECTIONS.validate_connection_one, + VALIDATE_ONE_MODE_INITSCRIPTS, + connections, + 0, + ) + + def test_802_1x_non_ethernet(self): + """ + should fail if a non-ethernet interface has 802.1x settings defined + """ + + input_connections = [ + { + "name": "802.1x-bond", + "state": "up", + "type": "bond", + "802.1x": { + "identity": "myhost", + "eap": "tls", + "private-key": "/etc/pki/tls/client.key", + "client-cert": "/etc/pki/tls/client.pem", + "private-key-password-flags": ["not-required"], + "system-ca-certs": True, + }, + } + ] + + connections = ARGS_CONNECTIONS.validate(input_connections) + + self.assertRaises( + n.ValidationError, + ARGS_CONNECTIONS.validate_connection_one, + VALIDATE_ONE_MODE_NM, + connections, + 0, + ) + def test_invalid_mac(self): self.maxDiff = None self.do_connections_check_invalid( @@ -2216,6 +2492,11 @@ class TestNM(unittest.TestCase): connections = nmutil.connection_list() self.assertIsNotNone(connections) + def test_path_to_glib_bytes(self): + result = Util.path_to_glib_bytes("/my/test/path") + self.assertIsInstance(result, Util.GLib().Bytes) + self.assertEqual(result.get_data(), b"file:///my/test/path\x00") + class TestUtils(unittest.TestCase): def test_check_output(self): @@ -2223,6 +2504,23 @@ class TestUtils(unittest.TestCase): self.assertEqual(res, "test\n") self.assertRaises(n.MyError, Util.check_output, ["false"]) + def test_convert_passwd_flags_nm(self): + test_cases = [ + ([], 0), + (["none"], 0), + (["agent-owned"], 1), + (["not-saved"], 2), + (["agent-owned", "not-saved"], 3), + (["not-required"], 4,), + (["agent-owned", "not-required"], 5), + (["not-saved", "not-required"], 6), + (["agent-owned", "not-saved", "not-required"], 7), + ] + + for test_case in test_cases: + result = Util.convert_passwd_flags_nm(test_case[0]) + self.assertEqual(result, test_case[1]) + class TestSysUtils(unittest.TestCase): def test_link_read_permaddress(self):