From ef07890ab28eb1a44c707ab657ef0152fe2eaafa Mon Sep 17 00:00:00 2001 From: David Mulder Date: Mon, 11 Nov 2024 15:10:18 -0700 Subject: [PATCH] Package Siemens Linux Entra SSO for Himmelblau This packages the linux-entra-sso.py, as well as all the configuration necessary to force install the extension for Firefox, Chrome, and Chromium. Signed-off-by: David Mulder --- Cargo.toml | 1 + images/rpm/Dockerfile.fedora41 | 2 +- images/rpm/Dockerfile.rawhide | 2 +- images/rpm/Dockerfile.rocky8 | 2 +- images/rpm/Dockerfile.rocky9 | 2 +- images/rpm/Dockerfile.sle15sp6 | 2 +- images/rpm/Dockerfile.tumbleweed | 2 +- images/ubuntu/Dockerfile.22.04 | 2 +- images/ubuntu/Dockerfile.24.04 | 2 +- src/sso/Cargo.toml | 40 +++ src/sso/LICENSE.md | 327 +++++++++++++++++++++++ src/sso/src/chrome/extension.json | 3 + src/sso/src/chrome/linux_entra_sso.json | 9 + src/sso/src/chrome/policies.json | 5 + src/sso/src/firefox/linux_entra_sso.json | 9 + src/sso/src/firefox/policies.json | 9 + src/sso/src/lib.rs | 0 src/sso/src/linux-entra-sso.py | 298 +++++++++++++++++++++ 18 files changed, 709 insertions(+), 8 deletions(-) create mode 100644 src/sso/Cargo.toml create mode 100644 src/sso/LICENSE.md create mode 100644 src/sso/src/chrome/extension.json create mode 100644 src/sso/src/chrome/linux_entra_sso.json create mode 100644 src/sso/src/chrome/policies.json create mode 100644 src/sso/src/firefox/linux_entra_sso.json create mode 100644 src/sso/src/firefox/policies.json create mode 100644 src/sso/src/lib.rs create mode 100755 src/sso/src/linux-entra-sso.py diff --git a/Cargo.toml b/Cargo.toml index 37399f9..807470f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "src/file_permissions", "src/broker", "src/sshd-config", + "src/sso", ] resolver = "2" diff --git a/images/rpm/Dockerfile.fedora41 b/images/rpm/Dockerfile.fedora41 index 5bea78f..b0647bc 100644 --- a/images/rpm/Dockerfile.fedora41 +++ b/images/rpm/Dockerfile.fedora41 @@ -37,4 +37,4 @@ WORKDIR /himmelblau RUN cargo install cargo-generate-rpm # Build the project and create the RPM package -CMD cargo clean && cargo build --release && strip -s target/release/*.so && strip -s target/release/aad-tool && strip -s target/release/himmelblaud && strip -s target/release/himmelblaud_tasks && strip -s target/release/broker && cargo generate-rpm -p src/daemon && cargo generate-rpm -p src/nss && cargo generate-rpm -p src/pam && cargo generate-rpm -p src/sshd-config +CMD cargo clean && cargo build --release && strip -s target/release/*.so && strip -s target/release/aad-tool && strip -s target/release/himmelblaud && strip -s target/release/himmelblaud_tasks && strip -s target/release/broker && cargo generate-rpm -p src/daemon && cargo generate-rpm -p src/nss && cargo generate-rpm -p src/pam && cargo generate-rpm -p src/sshd-config && cargo generate-rpm -p src/sso diff --git a/images/rpm/Dockerfile.rawhide b/images/rpm/Dockerfile.rawhide index 0c877b5..493f0f4 100644 --- a/images/rpm/Dockerfile.rawhide +++ b/images/rpm/Dockerfile.rawhide @@ -37,4 +37,4 @@ WORKDIR /himmelblau RUN cargo install cargo-generate-rpm # Build the project and create the RPM package -CMD cargo clean && cargo build --release && strip -s target/release/*.so && strip -s target/release/aad-tool && strip -s target/release/himmelblaud && strip -s target/release/himmelblaud_tasks && strip -s target/release/broker && cargo generate-rpm -p src/daemon && cargo generate-rpm -p src/nss && cargo generate-rpm -p src/pam && cargo generate-rpm -p src/sshd-config +CMD cargo clean && cargo build --release && strip -s target/release/*.so && strip -s target/release/aad-tool && strip -s target/release/himmelblaud && strip -s target/release/himmelblaud_tasks && strip -s target/release/broker && cargo generate-rpm -p src/daemon && cargo generate-rpm -p src/nss && cargo generate-rpm -p src/pam && cargo generate-rpm -p src/sshd-config && cargo generate-rpm -p src/sso diff --git a/images/rpm/Dockerfile.rocky8 b/images/rpm/Dockerfile.rocky8 index dde600b..353ac1c 100644 --- a/images/rpm/Dockerfile.rocky8 +++ b/images/rpm/Dockerfile.rocky8 @@ -54,4 +54,4 @@ WORKDIR /himmelblau RUN cargo install cargo-generate-rpm # Build the project and create the .deb package -CMD cargo clean && cargo build --release && strip -s target/release/*.so && strip -s target/release/aad-tool && strip -s target/release/himmelblaud && strip -s target/release/himmelblaud_tasks && strip -s target/release/broker && cargo generate-rpm -p src/daemon && cargo generate-rpm -p src/nss && cargo generate-rpm -p src/pam && cargo generate-rpm -p src/sshd-config +CMD cargo clean && cargo build --release && strip -s target/release/*.so && strip -s target/release/aad-tool && strip -s target/release/himmelblaud && strip -s target/release/himmelblaud_tasks && strip -s target/release/broker && cargo generate-rpm -p src/daemon && cargo generate-rpm -p src/nss && cargo generate-rpm -p src/pam && cargo generate-rpm -p src/sshd-config && cargo generate-rpm -p src/sso diff --git a/images/rpm/Dockerfile.rocky9 b/images/rpm/Dockerfile.rocky9 index ea50e68..5380ed2 100644 --- a/images/rpm/Dockerfile.rocky9 +++ b/images/rpm/Dockerfile.rocky9 @@ -60,4 +60,4 @@ WORKDIR /himmelblau RUN cargo install cargo-generate-rpm # Build the project and create the .deb package -CMD cargo clean && cargo build --release && strip -s target/release/*.so && strip -s target/release/aad-tool && strip -s target/release/himmelblaud && strip -s target/release/himmelblaud_tasks && strip -s target/release/broker && cargo generate-rpm -p src/daemon && cargo generate-rpm -p src/nss && cargo generate-rpm -p src/pam && cargo generate-rpm -p src/sshd-config +CMD cargo clean && cargo build --release && strip -s target/release/*.so && strip -s target/release/aad-tool && strip -s target/release/himmelblaud && strip -s target/release/himmelblaud_tasks && strip -s target/release/broker && cargo generate-rpm -p src/daemon && cargo generate-rpm -p src/nss && cargo generate-rpm -p src/pam && cargo generate-rpm -p src/sshd-config && cargo generate-rpm -p src/sso diff --git a/images/rpm/Dockerfile.sle15sp6 b/images/rpm/Dockerfile.sle15sp6 index 0329dec..c7fe854 100644 --- a/images/rpm/Dockerfile.sle15sp6 +++ b/images/rpm/Dockerfile.sle15sp6 @@ -38,4 +38,4 @@ WORKDIR /himmelblau RUN cargo install cargo-generate-rpm # Build the project and create the RPM package -CMD cargo clean && cargo build --release && strip -s target/release/*.so && strip -s target/release/aad-tool && strip -s target/release/himmelblaud && strip -s target/release/himmelblaud_tasks && strip -s target/release/broker && cargo generate-rpm -p src/daemon && cargo generate-rpm -p src/nss && cargo generate-rpm -p src/pam && cargo generate-rpm -p src/sshd-config +CMD cargo clean && cargo build --release && strip -s target/release/*.so && strip -s target/release/aad-tool && strip -s target/release/himmelblaud && strip -s target/release/himmelblaud_tasks && strip -s target/release/broker && cargo generate-rpm -p src/daemon && cargo generate-rpm -p src/nss && cargo generate-rpm -p src/pam && cargo generate-rpm -p src/sshd-config && cargo generate-rpm -p src/sso diff --git a/images/rpm/Dockerfile.tumbleweed b/images/rpm/Dockerfile.tumbleweed index 97fca35..719a60d 100644 --- a/images/rpm/Dockerfile.tumbleweed +++ b/images/rpm/Dockerfile.tumbleweed @@ -38,4 +38,4 @@ WORKDIR /himmelblau RUN cargo install cargo-generate-rpm # Build the project and create the RPM package -CMD cargo clean && cargo build --release && strip -s target/release/*.so && strip -s target/release/aad-tool && strip -s target/release/himmelblaud && strip -s target/release/himmelblaud_tasks && strip -s target/release/broker && cargo generate-rpm -p src/daemon && cargo generate-rpm -p src/nss && cargo generate-rpm -p src/pam && cargo generate-rpm -p src/sshd-config +CMD cargo clean && cargo build --release && strip -s target/release/*.so && strip -s target/release/aad-tool && strip -s target/release/himmelblaud && strip -s target/release/himmelblaud_tasks && strip -s target/release/broker && cargo generate-rpm -p src/daemon && cargo generate-rpm -p src/nss && cargo generate-rpm -p src/pam && cargo generate-rpm -p src/sshd-config && cargo generate-rpm -p src/sso diff --git a/images/ubuntu/Dockerfile.22.04 b/images/ubuntu/Dockerfile.22.04 index ced5423..3949078 100644 --- a/images/ubuntu/Dockerfile.22.04 +++ b/images/ubuntu/Dockerfile.22.04 @@ -49,4 +49,4 @@ WORKDIR /himmelblau RUN cargo install cargo-deb # Build the project and create the .deb package -CMD cargo clean && cargo deb --deb-revision=ubuntu22.04 -p himmelblaud && cargo deb --deb-revision=ubuntu22.04 -p nss_himmelblau && cargo deb --deb-revision=ubuntu22.04 -p pam_himmelblau && cargo deb --deb-revision=ubuntu22.04 -p sshd-config +CMD cargo clean && cargo deb --deb-revision=ubuntu22.04 -p himmelblaud && cargo deb --deb-revision=ubuntu22.04 -p nss_himmelblau && cargo deb --deb-revision=ubuntu22.04 -p pam_himmelblau && cargo deb --deb-revision=ubuntu22.04 -p sshd-config && cargo deb --deb-revision=ubuntu22.04 -p sso diff --git a/images/ubuntu/Dockerfile.24.04 b/images/ubuntu/Dockerfile.24.04 index 19201cd..2d217a4 100644 --- a/images/ubuntu/Dockerfile.24.04 +++ b/images/ubuntu/Dockerfile.24.04 @@ -49,4 +49,4 @@ WORKDIR /himmelblau RUN cargo install cargo-deb # Build the project and create the .deb package -CMD cargo clean && cargo deb --deb-revision=ubuntu24.04 -p himmelblaud && cargo deb --deb-revision=ubuntu24.04 -p nss_himmelblau && cargo deb --deb-revision=ubuntu24.04 -p pam_himmelblau && cargo deb --deb-revision=ubuntu24.04 -p sshd-config +CMD cargo clean && cargo deb --deb-revision=ubuntu24.04 -p himmelblaud && cargo deb --deb-revision=ubuntu24.04 -p nss_himmelblau && cargo deb --deb-revision=ubuntu24.04 -p pam_himmelblau && cargo deb --deb-revision=ubuntu24.04 -p sshd-config && cargo deb --deb-revision=ubuntu24.04 -p sso diff --git a/src/sso/Cargo.toml b/src/sso/Cargo.toml new file mode 100644 index 0000000..2d6f50c --- /dev/null +++ b/src/sso/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "sso" +description = "Entra ID SSO via Himmelblau Identity Broker" +version.workspace = true +authors.workspace = true +rust-version.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[package.metadata.deb] +name = "himmelblau-sso" +depends = ["python3-pydbus"] +assets = [ + ["src/linux-entra-sso.py", "usr/bin/linux-entra-sso", "755"], + ["src/firefox/linux_entra_sso.json", "usr/lib/mozilla/native-messaging-hosts/", "644"], + ["src/firefox/policies.json", "etc/firefox/policies/", "644"], + ["src/chrome/linux_entra_sso.json", "etc/opt/chrome/native-messaging-hosts/", "644"], + ["src/chrome/linux_entra_sso.json", "etc/chromium/native-messaging-hosts/", "644"], + ["src/chrome/extension.json", "usr/share/google-chrome/extensions/jlnfnnolkbjieggibinobhkjdfbpcohn.json", "644"], + ["src/chrome/policies.json", "etc/opt/chrome/policies/managed/himmelblau.json", "644"], + ["src/chrome/policies.json", "etc/chromium/policies/managed/himmelblau.json", "644"], +] + +[package.metadata.generate-rpm] +name = "himmelblau-sso" +assets = [ + { source = "src/linux-entra-sso.py", dest = "/usr/bin/linux-entra-sso", mode = "755" }, + { source = "src/firefox/linux_entra_sso.json", dest = "/usr/lib64/mozilla/native-messaging-hosts/", mode = "644" }, + { source = "src/firefox/policies.json", dest = "/etc/firefox/policies/", mode = "644" }, + { source = "src/chrome/linux_entra_sso.json", dest = "/etc/opt/chrome/native-messaging-hosts/", mode = "644" }, + { source = "src/chrome/linux_entra_sso.json", dest = "/etc/chromium/native-messaging-hosts/", mode = "644" }, + { source = "src/chrome/extension.json", dest = "/usr/share/google-chrome/extensions/jlnfnnolkbjieggibinobhkjdfbpcohn.json", mode = "644" }, + { source = "src/chrome/policies.json", dest = "/etc/opt/chrome/policies/managed/himmelblau.json", mode = "644" }, + { source = "src/chrome/policies.json", dest = "/etc/chromium/policies/managed/himmelblau.json", mode = "644" }, +] + +[package.metadata.generate-rpm.requires] +python3-pydbus = "*" diff --git a/src/sso/LICENSE.md b/src/sso/LICENSE.md new file mode 100644 index 0000000..74dee48 --- /dev/null +++ b/src/sso/LICENSE.md @@ -0,0 +1,327 @@ +# Mozilla Public License Version 2.0 + +1. Definitions + +--- + +1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation +of, or owns Covered Software. + +1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a +Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" means Covered Software of a particular Contributor. + +1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the +notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source +Code Form, in each case including portions thereof. + +1.5. "Incompatible With Secondary Licenses" means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" means any form of the work other than Source Code Form. + +1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate +file or files, that is not Covered Software. + +1.8. "License" means this document. + +1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the +time of the initial grant or subsequently, any and all of the rights conveyed by this License. + +1.10. "Modifications" means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, +method, process, and apparatus claims, in any patent Licensable by such Contributor that would be +infringed, but for the grant of the License, by the making, using, selling, offering for sale, +having made, import, or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser +General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any +later versions of those licenses. + +1.13. "Source Code Form" means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. +For legal entities, "You" includes any entity that controls, is controlled by, or is under common +control with You. For purposes of this definition, "control" means (a) the power, direct or +indirect, to cause the direction or management of such entity, whether by contract or otherwise, or +(b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of +such entity. + +2. License Grants and Conditions + +--- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) Licensable by such +Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise +exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger +Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, +and otherwise transfer either its Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution become effective for each +Contribution on the date the Contributor first distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under this License. No additional +rights or licenses will be implied from the distribution or licensing of Covered Software under this +License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: + +(a) for any code that a Contributor has removed from Covered Software; or + +(b) for infringements caused by: (i) Your and any other third party's modifications of Covered +Software, or (ii) the combination of its Contributions with other software (except as part of its +Contributor Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of its Contributions. + +This License does not grant any rights in the trademarks, service marks, or logos of any Contributor +(except as may be necessary to comply with the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to distribute the Covered Software +under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary +License (if permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its Contributions are its original +creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this +License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under applicable copyright doctrines of +fair use, fair dealing, or other equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. + +3. Responsibilities + +--- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any Modifications that You +create or to which You contribute, must be under the terms of this License. You must inform +recipients that the Source Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not attempt to alter or restrict +the recipients' rights in the Source Code Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code Form, as described in Section +3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source +Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution +to the recipient; and + +(b) You may distribute such Executable Form under the terms of this License, or sublicense it under +different terms, provided that the license for the Executable Form does not attempt to limit or +alter the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, provided that You also +comply with the requirements of this License for the Covered Software. If the Larger Work is a +combination of Covered Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this License permits You to +additionally distribute such Covered Software under the terms of such Secondary License(s), so that +the recipient of the Larger Work may, at their option, further distribute the Covered Software under +the terms of either this License or such Secondary License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices (including copyright notices, +patent notices, disclaimers of warranty, or limitations of liability) contained within the Source +Code Form of the Covered Software, except that You may alter any license notices to the extent +required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability +obligations to one or more recipients of Covered Software. However, You may do so only on Your own +behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such +warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree +to indemnify every Contributor for any liability incurred by such Contributor as a result of +warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of +warranty and limitations of liability specific to any jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + +--- + +If it is impossible for You to comply with any of the terms of this License with respect to some or +all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply +with the terms of this License to the maximum extent possible; and (b) describe the limitations and +the code they affect. Such description must be placed in a text file included with all distributions +of the Covered Software under this License. Except to the extent prohibited by statute or +regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be +able to understand it. + +5. Termination + +--- + +5.1. The rights granted under this License will terminate automatically if You fail to comply with +any of its terms. However, if You become compliant, then the rights granted under this License from +a particular Contributor are reinstated (a) provisionally, unless and until such Contributor +explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor +fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an +ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this +is the first time You have received notice of non-compliance with this License from such +Contributor, and You become compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent infringement claim +(excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a +Contributor Version directly or indirectly infringes any patent, then the rights granted to You by +any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements +(excluding distributors and resellers) which have been validly granted by You or Your distributors +under this License prior to termination shall survive termination. + +--- + +- + - +- + 6. Disclaimer of Warranty * +- ------------------------- * +- + - +- Covered Software is provided under this License on an "as is" * +- basis, without warranty of any kind, either expressed, implied, or * +- statutory, including, without limitation, warranties that the * +- Covered Software is free of defects, merchantable, fit for a * +- particular purpose or non-infringing. The entire risk as to the * +- quality and performance of the Covered Software is with You. * +- Should any Covered Software prove defective in any respect, You * +- (not any Contributor) assume the cost of any necessary servicing, * +- repair, or correction. This disclaimer of warranty constitutes an * +- essential part of this License. No use of any Covered Software is * +- authorized under this License except under this disclaimer. * +- + - + +--- + +--- + +- + - +- + 7. Limitation of Liability * +- -------------------------- * +- + - +- Under no circumstances and under no legal theory, whether tort * +- (including negligence), contract, or otherwise, shall any * +- Contributor, or anyone who distributes Covered Software as * +- permitted above, be liable to You for any direct, indirect, * +- special, incidental, or consequential damages of any character * +- including, without limitation, damages for lost profits, loss of * +- goodwill, work stoppage, computer failure or malfunction, or any * +- and all other commercial damages or losses, even if such party * +- shall have been informed of the possibility of such damages. This * +- limitation of liability shall not apply to liability for death or * +- personal injury resulting from such party's negligence to the * +- extent applicable law prohibits such limitation. Some * +- jurisdictions do not allow the exclusion or limitation of * +- incidental or consequential damages, so this exclusion and * +- limitation may not apply to You. * +- + - + +--- + +8. Litigation + +--- + +Any litigation relating to this License may be brought only in the courts of a jurisdiction where +the defendant maintains its principal place of business and such litigation shall be governed by +laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this +Section shall prevent a party's ability to bring cross-claims or counter-claims. + +9. Miscellaneous + +--- + +This License represents the complete agreement concerning the subject matter hereof. If any +provision of this License is held to be unenforceable, such provision shall be reformed only to the +extent necessary to make it enforceable. Any law or regulation which provides that the language of a +contract shall be construed against the drafter shall not be used to construe this License against a +Contributor. + +10. Versions of the License + +--- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the +license steward has the right to modify or publish new versions of this License. Each version will +be given a distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version of the License under which +You originally received the Covered Software, or under the terms of any subsequent version published +by the license steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to create a new license for such +software, you may create and use a modified version of this License if you rename the license and +remove any references to the name of the license steward (except to note that such modified license +differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + +If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the +terms of this version of the License, the notice described in Exhibit B of this License must be +attached. + +## Exhibit A - Source Code Form License Notice + +This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of +the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then You may include the +notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be +likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +## Exhibit B - "Incompatible With Secondary Licenses" Notice + +This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public +License, v. 2.0. diff --git a/src/sso/src/chrome/extension.json b/src/sso/src/chrome/extension.json new file mode 100644 index 0000000..9b49781 --- /dev/null +++ b/src/sso/src/chrome/extension.json @@ -0,0 +1,3 @@ +{ + "external_update_url": "https://clients2.google.com/service/update2/crx" +} diff --git a/src/sso/src/chrome/linux_entra_sso.json b/src/sso/src/chrome/linux_entra_sso.json new file mode 100644 index 0000000..50a0e32 --- /dev/null +++ b/src/sso/src/chrome/linux_entra_sso.json @@ -0,0 +1,9 @@ +{ + "name": "linux_entra_sso", + "description": "Entra ID SSO via Microsoft Identity Broker", + "path": "/usr/bin/linux-entra-sso", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://jlnfnnolkbjieggibinobhkjdfbpcohn/" + ] +} diff --git a/src/sso/src/chrome/policies.json b/src/sso/src/chrome/policies.json new file mode 100644 index 0000000..1a886ff --- /dev/null +++ b/src/sso/src/chrome/policies.json @@ -0,0 +1,5 @@ +{ + "ExtensionInstallForcelist": [ + "jlnfnnolkbjieggibinobhkjdfbpcohn" + ] +} diff --git a/src/sso/src/firefox/linux_entra_sso.json b/src/sso/src/firefox/linux_entra_sso.json new file mode 100644 index 0000000..c00b071 --- /dev/null +++ b/src/sso/src/firefox/linux_entra_sso.json @@ -0,0 +1,9 @@ +{ + "name": "linux_entra_sso", + "description": "Entra ID SSO via Microsoft Identity Broker", + "path": "/usr/bin/linux-entra-sso", + "type": "stdio", + "allowed_extensions": [ + "linux-entra-sso@example.com" + ] +} diff --git a/src/sso/src/firefox/policies.json b/src/sso/src/firefox/policies.json new file mode 100644 index 0000000..382a9a5 --- /dev/null +++ b/src/sso/src/firefox/policies.json @@ -0,0 +1,9 @@ +{ + "policies": { + "Extensions": { + "Install": [ + "https://github.com/siemens/linux-entra-sso/releases/download/v1.1.0/linux_entra_sso-1.1.0.xpi" + ] + } + } +} diff --git a/src/sso/src/lib.rs b/src/sso/src/lib.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/sso/src/linux-entra-sso.py b/src/sso/src/linux-entra-sso.py new file mode 100755 index 0000000..ca47fba --- /dev/null +++ b/src/sso/src/linux-entra-sso.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: Copyright 2024 Siemens AG + +# pylint: disable=missing-docstring,invalid-name + +# Renable invalid-name check, it should only cover the module name +# pylint: enable=invalid-name + +import argparse +import sys +import json +import struct +import uuid +import ctypes +import time +from signal import SIGINT +from threading import Thread, Lock +from gi.repository import GLib, Gio +from pydbus import SessionBus + +# version is replaced on installation +LINUX_ENTRA_SSO_VERSION = "" + +# the ssoUrl is a mandatory parameter when requesting a PRT SSO +# Cookie, but the correct value is not checked as of 30.05.2024 +# by the authorization backend. By that, a static (fallback) +# value can be used, if no real value is provided. +SSO_URL_DEFAULT = "https://login.microsoftonline.com/" +EDGE_BROWSER_CLIENT_ID = "d7b530a4-7680-4c23-a8bf-c52c121d2e87" +BROKER_START_TIMEOUT = 5 +# dbus start service reply codes +START_REPLY_SUCCESS = 1 +START_REPLY_ALREADY_RUNNING = 2 +# prctl constants +PR_SET_PDEATHSIG = 1 + + +class NativeMessaging: + @staticmethod + def get_message(): + """ + Read a message from stdin and decode it. + """ + raw_length = sys.stdin.buffer.read(4) + if not raw_length: + sys.exit(0) + message_length = struct.unpack('@I', raw_length)[0] + message = sys.stdin.buffer.read(message_length).decode('utf-8') + return json.loads(message) + + @staticmethod + def encode_message(message_content): + """ + Encode a message for transmission, given its content + """ + encoded_content = json.dumps(message_content, separators=(',', ':')) \ + .encode('utf-8') + encoded_length = struct.pack('@I', len(encoded_content)) + return {'length': encoded_length, 'content': encoded_content} + + @staticmethod + def send_message(encoded_message): + """ + Send an encoded message to stdout + """ + sys.stdout.buffer.write(encoded_message['length']) + sys.stdout.buffer.write(encoded_message['content']) + sys.stdout.buffer.flush() + + +class SsoMib: + BROKER_NAME = 'com.microsoft.identity.broker1' + BROKER_PATH = '/com/microsoft/identity/broker1' + GRAPH_SCOPES = ["https://graph.microsoft.com/.default"] + + def __init__(self, daemon=False): + self._bus = SessionBus() + self.broker = None + self.session_id = uuid.uuid4() + self._state_changed_cb = None + if daemon: + self._introspect_broker(fail_on_error=False) + self._monitor_bus() + + def _introspect_broker(self, fail_on_error=True): + timeout = time.time() + BROKER_START_TIMEOUT + while not self.broker and time.time() < timeout: + try: + self.broker = self._bus.get(self.BROKER_NAME, self.BROKER_PATH) + return + except GLib.Error as err: + # GDBus.Error:org.freedesktop.dbus.errors.UnknownObject: + # Introspecting on non-existant object + # See https://github.com/siemens/linux-entra-sso/issues/33 + if err.matches(Gio.io_error_quark(), + Gio.IOErrorEnum.DBUS_ERROR): + time.sleep(0.1) + continue + if fail_on_error: + raise RuntimeError("Could not start broker") + + def _monitor_bus(self): + self._bus.subscribe( + sender="org.freedesktop.DBus", + object="/org/freedesktop/DBus", + signal="NameOwnerChanged", + arg0=self.BROKER_NAME, + signal_fired=self._broker_state_changed) + + def _broker_state_changed(self, sender, object, iface, signal, params): \ + # pylint: disable=redefined-builtin,too-many-arguments + _ = (sender, object, iface, signal) + # params = (name, old_owner, new_owner) + new_owner = params[2] + if new_owner: + self._introspect_broker() + else: + # we need to ensure that the next dbus call will + # wait until the broker is fully initialized again + self.broker = None + if self._state_changed_cb: + self._state_changed_cb(new_owner) + + def on_broker_state_changed(self, callback): + """ + Register a callback to be called when the broker state changes. + The callback should accept a single boolean argument, indicating + if the broker is online or not. + """ + self._state_changed_cb = callback + + @staticmethod + def _get_auth_parameters(account, scopes): + return { + 'account': account, + 'additionalQueryParametersForAuthorization': {}, + 'authority': 'https://login.microsoftonline.com/common', + 'authorizationType': 8, # OAUTH2 + 'clientId': EDGE_BROWSER_CLIENT_ID, + 'redirectUri': 'https://login.microsoftonline.com' + '/common/oauth2/nativeclient', + 'requestedScopes': scopes, + 'username': account['username'], + } + + def get_accounts(self): + self._introspect_broker() + context = { + 'clientId': EDGE_BROWSER_CLIENT_ID, + 'redirectUri': str(self.session_id) + } + resp = self.broker.getAccounts('0.0', + str(self.session_id), + json.dumps(context)) + return json.loads(resp) + + def acquire_prt_sso_cookie(self, account, sso_url, scopes=GRAPH_SCOPES): \ + # pylint: disable=dangerous-default-value + self._introspect_broker() + request = { + 'account': account, + 'authParameters': SsoMib._get_auth_parameters(account, scopes), + 'ssoUrl': sso_url + } + token = json.loads(self.broker.acquirePrtSsoCookie( + '0.0', str(self.session_id), json.dumps(request))) + return token + + def acquire_token_silently(self, account, scopes=GRAPH_SCOPES): \ + # pylint: disable=dangerous-default-value + self._introspect_broker() + request = { + 'account': account, + 'authParameters': SsoMib._get_auth_parameters(account, scopes), + } + token = json.loads(self.broker.acquireTokenSilently( + '0.0', str(self.session_id), json.dumps(request))) + return token + + def get_broker_version(self): + self._introspect_broker() + params = json.dumps({"msalCppVersion": LINUX_ENTRA_SSO_VERSION}) + resp = json.loads( + self.broker.getLinuxBrokerVersion('0.0', + str(self.session_id), + params)) + resp["native"] = LINUX_ENTRA_SSO_VERSION + return resp + + +def run_as_native_messaging(): + iomutex = Lock() + no_broker = {'error': 'Broker not available'} + + def respond(command, message): + NativeMessaging.send_message( + NativeMessaging.encode_message( + {"command": command, "message": message})) + + def notify_state_change(online): + with iomutex: + respond("brokerStateChanged", 'online' if online else 'offline') + + def handle_command(cmd, received_message): + if cmd == "acquirePrtSsoCookie": + account = received_message['account'] + sso_url = received_message['ssoUrl'] or SSO_URL_DEFAULT + token = ssomib.acquire_prt_sso_cookie(account, sso_url) + respond(cmd, token) + elif cmd == "acquireTokenSilently": + account = received_message['account'] + scopes = received_message.get('scopes') or ssomib.GRAPH_SCOPES + token = ssomib.acquire_token_silently(account, scopes) + respond(cmd, token) + elif cmd == "getAccounts": + respond(cmd, ssomib.get_accounts()) + elif cmd == "getVersion": + respond(cmd, ssomib.get_broker_version()) + + def run_dbus_monitor(): + # inform other side about initial state + notify_state_change(bool(ssomib.broker)) + loop = GLib.MainLoop() + loop.run() + + def register_terminate_with_parent(): + libc = ctypes.CDLL("libc.so.6") + libc.prctl(PR_SET_PDEATHSIG, SIGINT, 0, 0, 0) + + print("Running as native messaging instance.", file=sys.stderr) + print("For interactive mode, start with --interactive", file=sys.stderr) + + # on chrome and chromium, the parent process does not reliably + # terminate the process when the parent process is killed. + register_terminate_with_parent() + + ssomib = SsoMib(daemon=True) + ssomib.on_broker_state_changed(notify_state_change) + monitor = Thread(target=run_dbus_monitor) + monitor.start() + while True: + received_message = NativeMessaging.get_message() + with iomutex: + cmd = received_message['command'] + try: + handle_command(cmd, received_message) + except Exception: # pylint: disable=broad-except + respond(cmd, no_broker) + + +def run_interactive(): + parser = argparse.ArgumentParser() + parser.add_argument("-i", "--interactive", action="store_true", + help="run in interactive mode") + parser.add_argument("-a", "--account", type=int, default=0, + help="account index to use for operations") + parser.add_argument("-s", "--ssoUrl", default=SSO_URL_DEFAULT, + help="ssoUrl part of SSO PRT cookie request") + parser.add_argument("command", choices=["getAccounts", + "getVersion", + "acquirePrtSsoCookie", + "acquireTokenSilently", + "monitor"]) + args = parser.parse_args() + + monitor_mode = args.command == "monitor" + ssomib = SsoMib(daemon=monitor_mode) + if monitor_mode: + print("Monitoring D-Bus for broker availability.") + ssomib.on_broker_state_changed( + lambda online: print(f"{ssomib.BROKER_NAME} is now " + f"{'online' if online else 'offline'}.")) + GLib.MainLoop().run() + return + + accounts = ssomib.get_accounts() + if args.command == 'getAccounts': + json.dump(accounts, indent=2, fp=sys.stdout) + elif args.command == 'getVersion': + json.dump(ssomib.get_broker_version(), indent=2, fp=sys.stdout) + elif args.command == "acquirePrtSsoCookie": + account = accounts['accounts'][args.account] + cookie = ssomib.acquire_prt_sso_cookie(account, args.ssoUrl) + json.dump(cookie, indent=2, fp=sys.stdout) + elif args.command == "acquireTokenSilently": + account = accounts['accounts'][args.account] + token = ssomib.acquire_token_silently(account) + json.dump(token, indent=2, fp=sys.stdout) + # add newline + print() + + +if __name__ == '__main__': + if '--interactive' in sys.argv or '-i' in sys.argv: + run_interactive() + else: + run_as_native_messaging()