information

This commit is contained in:
2025-01-18 11:56:17 -05:00
commit b84b748c03
26 changed files with 7461 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
target
Cargo.lock

88
Cargo.toml Normal file
View File

@@ -0,0 +1,88 @@
[package]
name = "bell_system"
version = "0.0.1"
edition = "2021"
authors = ["Ronaldson Bellande <ronaldsonbellande@gmail.com>"]
description = "Advanced privilege escalation system with multi-level security and compliance features"
license = "GPL-3.0-or-later"
repository = "https://github.com/Architecture-Mechanism/bell"
documentation = "https://bellande-architecture-mechanism-research-innovation-center.org/bell/docs"
readme = "README.md"
keywords = ["privilege-escalation", "security", "authentication", "system", "bellande_bell_system"]
categories = ["command-line-utilities", "authentication"]
[lib]
name = "bell"
path = "src/bell.rs"
[dependencies]
# Core async runtime
tokio = { version = "1.28", features = ["full"] }
# Command line parsing
structopt = "0.3.26"
# Logging and monitoring
log = "0.4.17"
log4rs = "1.2.0"
sysinfo = "0.29"
# Error handling
thiserror = "1.0.40"
anyhow = "1.0"
# System interaction
nix = "0.26.2"
libc = { version = "0.2.144", features = ["extra_traits"] }
libseccomp = "0.3"
syscallz = "0.17"
# Authentication and security
totp-rs = { version = "5.0.2", features = ["gen_secret"] }
rand = "0.8.5"
rand_core = "0.6.4"
argon2 = { version = "0.5", features = ["std", "password-hash"] }
# Database
rusqlite = { version = "0.29", features = ["bundled"] }
# Serialization and configuration
serde = { version = "1.0.163", features = ["derive"] }
serde_json = "1.0"
toml = "0.7.3"
# Email
lettre = { version = "0.10", features = ["rustls-tls", "smtp-transport"] }
# Time
chrono = { version = "0.4", features = ["serde"] }
# Cryptography
aes-gcm = { version = "0.10.1", features = ["std"] }
sha2 = "0.10.6"
# Networking
ipnetwork = "0.20.0"
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
walkdir = "2.4"
regex = "1.5"
local-ip-address = "0.5"
base64 = "0.21"
[target.'cfg(unix)'.dependencies]
users = "0.11.0"
[features]
default = ["standard"]
standard = []
enterprise = ["hsm", "advanced-audit", "network-isolation"]
hsm = []
advanced-audit = []
network-isolation = []
[package.metadata.bellande]
organization = "Architecture-Mechanism"
project-type = "system-security"
supported-os = ["bellandeos", "linux", "macos"]
minimum-rust-version = "1.70.0"
security-contact = "security@bellande-architecture-mechanism-research-innovation-center.org"

674
LICENSE Normal file
View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
bell Copyright (C) 2024 Bellande Architecture Mechanism Research Innovation Center
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
bell Copyright (C) 2024 Bellande Architecture Mechanism Research Innovation Center
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

416
README.md Normal file
View File

@@ -0,0 +1,416 @@
# bell
---
title: Bell Privilege Escalation System
author: Bellande Architecture Mechanism Research Innovation Center
version: 0.0.1
date: 2024
---
## Website Crates
- https://crates.io/crates/bell_system
### Installation
- `cargo add bell_system`
```
Name: bell_system
Summary: Bell is a comprehensive privilege escalation system designed for secure command execution with granular access controls, robust auditing, and compliance features
Home-page: github.com/Architecture-Mechanism/bell
Author: Ronaldson Bellande
Author-email: ronaldsonbellande@gmail.com
License: GNU General Public License v3.0
```
# Bell Privilege Escalation System
Bell is a comprehensive privilege escalation system designed for secure command execution with granular access controls, robust auditing, and compliance features
## Table of Contents
1. [Overview](#overview)
2. [Installation](#installation)
3. [Usage](#usage)
4. [Configuration](#configuration)
5. [Security Features](#security-features)
6. [OS-Specific Features](#os-specific-features)
7. [Best Practices](#best-practices)
8. [Troubleshooting](#troubleshooting)
9. [API Reference](#api-reference)
10. [License](#license)
## Overview
Bell is an advanced privilege escalation system designed for secure enterprise environments. It integrates hardware security modules, multi-factor authentication, and comprehensive audit logging.
### Key Features
* Multi-level privilege management
* Hardware Security Module (HSM) integration
* Two-factor authentication (TOTP)
* Network isolation capabilities
* Fine-grained access control
* Comprehensive audit logging
* Cross-platform support (Linux, MacOS, BellandeOS)
### Architecture
```
+------------------+ +------------------+ +------------------+
| Bell Client | --> | Bell Core | --> | Security Layer |
+------------------+ +------------------+ +------------------+
| | |
v v v
+------------------+ +------------------+ +------------------+
| Auth Module | | HSM Module | | Audit Module |
+------------------+ +------------------+ +------------------+
```
## Installation
Prerequisites
- Rust 1.70 or higher
- OpenSSL development libraries
- Hardware Security Module (optional)
- TOTP device/app for 2FA
git clone https://github.com/Architecture-Mechanism/bell.git
cd bell-system
# Build in release mode
cargo build --release
# Run tests
cargo test --all-features
# Install system-wide
sudo make install
## System Requirements
### Hardware Requirements
| Component | Minimum Specification |
|-----------|---------------------|
| CPU | x86_64 or ARM64 |
| RAM | 512MB |
| Disk Space | 1GB free |
### Operating System Support
| OS | Minimum Version |
|----|----------------|
| Linux | 4.19+ |
| MacOS | 10.15+ |
| BellandeOS | 0.1+ |
# Running Commands
```
bell run --privilege-level <level> --command <command> --args <args...>
bell run --privilege-level admin --command "/usr/bin/systemctl" --args "restart" "nginx"
bell run --privilege-level root --command "/usr/bin/apt" --args "update"
```
# User Management
## Adding Users
```
bell user add <username> --privilege <level>
# Examples
bell user add johndoe --privilege admin
bell user add service-account --privilege user
```
## Modifying Users
```
# Change password
bell user change-password <username>
# Change privilege
bell user change-privilege <username> <new-privilege>
# Remove user
bell user remove <username>
```
## Group Management
```
# Add to group
bell user add-to-group <username> <group>
# Remove from group
bell user remove-from-group <username> <group>
# List group members
bell group list-members <group>
```
## File Locations
```
/etc/bell/
├── config.bellande # Main configuration
├── users/ # User configurations
│ ├── admin.bellande
│ └── service.bellande
├── groups/ # Group configurations
│ ├── admins.bellande
│ └── services.bellande
└── security/ # Security policies
├── policy.bellande
└── rules.bellande
```
## Section Management
```
# View active sessions
bell session list
# Terminate session
bell session terminate <session-id>
# Refresh session
bell session refresh
```
## Log Management
```
# View logs
bell logs view --level error --since "1 hour ago"
# Export logs
bell logs export --format json --start "2024-01-01" --end "2024-01-31"
# Analyze logs
bell logs analyze --pattern "failed_auth" --report detailed
```
## MacOS Intergration
```
# FileVault management
bell run --privilege-level admin --command "fdesetup" --args "status"
# SIP verification
bell run --privilege-level bell --command "csrutil" --args "status"
# Keychain access
bell run --privilege-level admin --command "security" --args "list-keychains"
```
## Linux Security
```
# SELinux management
bell run --privilege-level admin --command "semanage" --args "login" "-l"
# AppArmor profiles
bell run --privilege-level root --command "aa-status"
# Kernel parameters
bell run --privilege-level bell --command "sysctl" --args "-a"
```
## BellandeOS Features
```
# Security status
bell run --privilege-level bell --command "bellctl" --args "security" "status"
# Kernel protection
bell run --privilege-level admin --command "bellctl" --args "kernel" "protect"
# System integrity
bell run --privilege-level root --command "bellctl" --args "verify" "system"
```
## Bellande Operating System Access
```
EXTENDED 5-LEVEL PERMISSION SYSTEM (77777)
========================================
BASIC PERMISSION VALUES
----------------------
Read (r) = 4
Write (w) = 2
Execute (x) = 1
PERMISSION NUMBER MEANINGS
------------------------
0 = --- = no access
1 = --x = execute only
2 = -w- = write only
3 = -wx = write and execute
4 = r-- = read only
5 = r-x = read and execute
6 = rw- = read and write
7 = rwx = read, write, and execute (full access)
POSITION MEANINGS (LEFT TO RIGHT)
-------------------------------
Position 1 (leftmost) = Owner/Bell
Position 2 = Root
Position 3 = Administration
Position 4 = Group
Position 5 (rightmost)= User
STANDARD PERMISSION: 77531
-------------------------
Owner (7) = rwx = 4+2+1 = 7
Root (7) = rwx = 4+2+1 = 7
Administration (5) = r-x = 4+0+1 = 5
Group (3) = -wx = 0+2+1 = 3
User (1) = --x = 0+0+1 = 1
DETAILED ACCESS LEVELS
--------------------
OWNER/BELL (Position 1)
- Value: 7 (rwx)
- Calculation: 4(read) + 2(write) + 1(execute) = 7
- Access:
* All system files and directories
* Core components
* Kernel level access
* Hardware level access
* Can override all permissions
* Complete system control
ROOT (Position 2)
- Value: 7 (rwx)
- Calculation: 4(read) + 2(write) + 1(execute) = 7
- Access:
* System files
* Configuration files
* Installation files
* Startup sequences
* Cannot access core components
* Cannot modify kernel
ADMINISTRATION (Position 3)
- Value: 5 (r-x)
- Calculation: 4(read) + 0(write) + 1(execute) = 5
- Access:
* Read system configurations
* Execute administrative tasks
* Manage users
* Cannot modify system files
* No core component access
* No kernel modifications
GROUP (Position 4)
- Value: 3 (-wx)
- Calculation: 0(read) + 2(write) + 1(execute) = 3
- Access:
* Modify group files
* Execute group programs
* Share within group
* No read outside group
* No system modifications
* Limited to group scope
USER (Position 5)
- Value: 1 (--x)
- Calculation: 0(read) + 0(write) + 1(execute) = 1
- Access:
* Execute allowed programs
* Access own directory
* Use basic utilities
* No system modifications
* No file modifications
* No read access outside home
COMMON PERMISSION COMBINATIONS
----------------------------
77000 - System Critical Files
Owner: 7 (rwx) = 4+2+1 : Full control
Root: 7 (rwx) = 4+2+1 : Full control
Admin: 0 (---) = 0+0+0 : No access
Group: 0 (---) = 0+0+0 : No access
User: 0 (---) = 0+0+0 : No access
Use: Core system files, kernel components
77530 - Administrative Tools
Owner: 7 (rwx) = 4+2+1 : Full control
Root: 7 (rwx) = 4+2+1 : Full control
Admin: 5 (r-x) = 4+0+1 : Read + Execute
Group: 3 (-wx) = 0+2+1 : Write + Execute
User: 0 (---) = 0+0+0 : No access
Use: System management tools, configuration files
75531 - Standard Applications
Owner: 7 (rwx) = 4+2+1 : Full control
Root: 5 (r-x) = 4+0+1 : Read + Execute
Admin: 5 (r-x) = 4+0+1 : Read + Execute
Group: 3 (-wx) = 0+2+1 : Write + Execute
User: 1 (--x) = 0+0+1 : Execute only
Use: Standard applications, user programs
PERMISSION GUIDELINES
-------------------
1. New Files/Directories
- Start restrictive (77000 for system)
- Add permissions as needed
- Document changes
2. Directory Requirements
- Need execute (x) to access
- Need read (r) to list contents
- Need write (w) to create/delete
3. Security Practices
- Use minimum needed permissions
- Regular permission checks
- Document all changes
- Monitor access patterns
4. Important Rules
- Higher positions override lower
- Cannot exceed upper level permissions
- Execute needed for directories
- Write permission alone is rarely used
EXAMPLES AND USE CASES
---------------------
77777 - NOT RECOMMENDED
- Gives full access to all levels
- Security risk
- Never use in production
77531 - STANDARD SECURE
- Owner: Full control
- Root: Full control
- Admin: Limited control
- Group: Write in scope
- User: Execute only
77000 - SYSTEM FILES
- Only Owner and Root access
- Maximum security
- Use for critical files
75531 - USER APPLICATIONS
- Limited Root access
- Admin can manage
- Group collaboration
- User can execute
```
# Command Line
```
bell [OPTIONS] COMMAND [ARGS]
Commands:
run Execute privileged command
user User management
group Group management
session Session management
logs Log management
debug Debug tools
help Show help information
Options:
-d, --debug Enable debug mode
-c, --config Config file location
-q, --quiet Suppress output
-v, --version Show version
-h, --help Show help
```
## License
Bell is distributed under the [GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html), see [LICENSE](https://github.com/Architecture-Mechanism/bell/blob/main/LICENSE) and [NOTICE](https://github.com/Architecture-Mechanism/bell/blob/main/LICENSE) for more information.
## Code of Conduct
Bell is distributed under the [CODE_OF_CONDUCT](https://github.com/Architecture-Mechanism/bell/blob/main/CODE_OF_CONDUCT.md) and [NOTICE](https://github.com/Architecture-Mechanism/bell/blob/main/CODE_OF_CONDUCT.md) for more information.

4
dependencies.bellande Normal file
View File

@@ -0,0 +1,4 @@
glob: "0.3.0"
tempfile: "3.2"
shellexpand: "3.1.0"
meval: "0.2"

3
git_scripts/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
fix_errors.sh
push.sh
repository_recal.sh

1
make_rust_executable.bellos Executable file
View File

@@ -0,0 +1 @@
bellande_rust_executable -d dependencies.bellande -s src -m bellos.rs -o executable/bellos

1
make_rust_executable.sh Executable file
View File

@@ -0,0 +1 @@
bellande_rust_executable -d dependencies.bellande -s src -m bellos.rs -o executable/bellos

253
src/audit/audit.rs Normal file
View File

@@ -0,0 +1,253 @@
// Copyright (C) 2024 Bellande Architecture Mechanism Research Innovation Center, Ronaldson Bellande
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::path::PathBuf;
use anyhow::{Context, Result};
use chrono::{DateTime, Local, Utc};
use lettre::message::header::ContentType;
use lettre::transport::smtp::authentication::Credentials;
use lettre::{Message, SmtpTransport, Transport};
use rusqlite::{params, Connection};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct AuditConfig {
log_file: PathBuf,
database_file: PathBuf,
max_log_size: u64,
rotation_count: u32,
alert_email: String,
smtp_server: String,
smtp_port: u16,
smtp_username: String,
smtp_password: String,
critical_events: Vec<String>,
}
impl Default for AuditConfig {
fn default() -> Self {
let os_specific_path = match std::env::consts::OS {
"macos" => PathBuf::from("/var/log/bell"),
"linux" => PathBuf::from("/var/log/bell"),
"bellandeos" => PathBuf::from("/bell/log"),
_ => PathBuf::from("./log"),
};
Self {
log_file: os_specific_path.join("audit.log"),
database_file: os_specific_path.join("audit.db"),
max_log_size: 10 * 1024 * 1024, // 10MB
rotation_count: 5,
alert_email: "admin@bellande-architecture-mechanism-research-innovation-center.org"
.to_string(),
smtp_server: "smtp.bellande-architecture-mechanism-research-innovation-center.org"
.to_string(),
smtp_port: 587,
smtp_username: "alerts@bellande-architecture-mechanism.org".to_string(),
smtp_password: "your_secure_password".to_string(),
critical_events: vec![
"AUTHENTICATION_FAILURE".to_string(),
"PERMISSION_DENIED".to_string(),
"SUSPICIOUS_ACTIVITY".to_string(),
"SECURITY_BREACH".to_string(),
"SYSTEM_MODIFICATION".to_string(),
],
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AuditEvent {
timestamp: DateTime<Utc>,
user: String,
event: String,
details: String,
system: String,
process_id: u32,
severity: EventSeverity,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum EventSeverity {
Info,
Warning,
Critical,
Emergency,
}
pub async fn log_audit_event(event: &str, user: &str, details: &str) -> Result<()> {
let config = AuditConfig::default();
let audit_event = create_audit_event(event, user, details);
// Ensure log directory exists
if let Some(parent) = config.log_file.parent() {
fs::create_dir_all(parent).context("Failed to create log directory")?;
}
// Check log rotation
check_and_rotate_logs(&config).await?;
// Write to log file
write_to_log_file(&config, &audit_event).await?;
// Write to database
log_to_database(&config, &audit_event).await?;
// Send alert if critical
if is_critical_event(&config, event) {
send_alert(&config, &audit_event).await?;
}
Ok(())
}
fn create_audit_event(event: &str, user: &str, details: &str) -> AuditEvent {
AuditEvent {
timestamp: Utc::now(),
user: user.to_string(),
event: event.to_string(),
details: details.to_string(),
system: std::env::consts::OS.to_string(),
process_id: std::process::id(),
severity: determine_severity(event),
}
}
async fn write_to_log_file(config: &AuditConfig, event: &AuditEvent) -> Result<()> {
let mut file = OpenOptions::new()
.append(true)
.create(true)
.open(&config.log_file)
.context("Failed to open audit log file")?;
let log_entry = format!(
"{} - User: {} - Event: {} - Details: {} - System: {} - PID: {} - Severity: {:?}\n",
event.timestamp.with_timezone(&Local),
event.user,
event.event,
event.details,
event.system,
event.process_id,
event.severity
);
file.write_all(log_entry.as_bytes())
.context("Failed to write to audit log")?;
Ok(())
}
async fn log_to_database(config: &AuditConfig, event: &AuditEvent) -> Result<()> {
let conn = Connection::open(&config.database_file).context("Failed to open database")?;
conn.execute(
"CREATE TABLE IF NOT EXISTS audit_log (
id INTEGER PRIMARY KEY,
timestamp TEXT NOT NULL,
user TEXT NOT NULL,
event TEXT NOT NULL,
details TEXT NOT NULL,
system TEXT NOT NULL,
process_id INTEGER NOT NULL,
severity TEXT NOT NULL
)",
[],
)
.context("Failed to create audit_log table")?;
conn.execute(
"INSERT INTO audit_log (timestamp, user, event, details, system, process_id, severity)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
params![
event.timestamp.to_rfc3339(),
event.user,
event.event,
event.details,
event.system,
event.process_id,
format!("{:?}", event.severity)
],
)
.context("Failed to insert log entry into database")?;
Ok(())
}
fn is_critical_event(config: &AuditConfig, event: &str) -> bool {
config.critical_events.contains(&event.to_string())
}
fn determine_severity(event: &str) -> EventSeverity {
match event {
"AUTHENTICATION_FAILURE" | "PERMISSION_DENIED" => EventSeverity::Warning,
"SUSPICIOUS_ACTIVITY" | "SECURITY_BREACH" => EventSeverity::Critical,
"SYSTEM_MODIFICATION" => EventSeverity::Emergency,
_ => EventSeverity::Info,
}
}
async fn send_alert(config: &AuditConfig, event: &AuditEvent) -> Result<()> {
let email = Message::builder()
.from(config.smtp_username.parse().context("Invalid from address")?)
.to(config.alert_email.parse().context("Invalid to address")?)
.subject(format!("Critical Security Alert: {}", event.event))
.header(ContentType::TEXT_PLAIN)
.body(format!(
"Critical security event detected:\n\nTimestamp: {}\nUser: {}\nEvent: {}\nDetails: {}\nSystem: {}\nProcess ID: {}\nSeverity: {:?}",
event.timestamp.with_timezone(&Local),
event.user,
event.event,
event.details,
event.system,
event.process_id,
event.severity
))
.context("Failed to build email")?;
let creds = Credentials::new(config.smtp_username.clone(), config.smtp_password.clone());
let mailer = SmtpTransport::relay(&config.smtp_server)
.context("Failed to create SMTP transport")?
.credentials(creds)
.port(config.smtp_port)
.build();
mailer.send(&email).context("Failed to send email")?;
Ok(())
}
async fn check_and_rotate_logs(config: &AuditConfig) -> Result<()> {
let metadata = fs::metadata(&config.log_file)?;
if metadata.len() >= config.max_log_size {
for i in (1..config.rotation_count).rev() {
let current = config.log_file.with_extension(format!("log.{}", i));
let next = config.log_file.with_extension(format!("log.{}", i + 1));
if current.exists() {
fs::rename(current, next)?;
}
}
let backup = config.log_file.with_extension("log.1");
fs::rename(&config.log_file, backup)?;
fs::File::create(&config.log_file)?;
}
Ok(())
}

2
src/audit/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod audit;
pub mod security_audit;

812
src/audit/security_audit.rs Normal file
View File

@@ -0,0 +1,812 @@
// Copyright (C) 2024 Bellande Architecture Mechanism Research Innovation Center, Ronaldson Bellande
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::collections::{HashMap, HashSet};
use std::fs::{self, File};
use std::io::BufReader;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Duration;
use anyhow::{Context, Result};
use log::info;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::os::unix::fs::PermissionsExt;
use sysinfo::{ProcessExt, System, SystemExt};
use walkdir::WalkDir;
use crate::audit::audit::log_audit_event;
use crate::config::config::Config;
#[derive(Debug, Serialize, Deserialize)]
pub struct SecurityAuditConfig {
pub critical_paths: Vec<PathBuf>,
pub suspicious_process_patterns: Vec<String>,
pub allowed_ports: HashSet<u16>,
pub file_hash_database: PathBuf,
pub scan_interval: Duration,
}
impl Default for SecurityAuditConfig {
fn default() -> Self {
SecurityAuditConfig {
critical_paths: get_os_critical_paths(),
suspicious_process_patterns: vec![
"crypto".to_string(),
"miner".to_string(),
"suspicious".to_string(),
],
allowed_ports: [80, 443, 22, 53].iter().cloned().collect(),
file_hash_database: PathBuf::from("file_hashes.db"),
scan_interval: Duration::from_secs(3600),
}
}
}
fn get_os_critical_paths() -> Vec<PathBuf> {
match std::env::consts::OS {
"macos" => vec![
PathBuf::from("/etc"),
PathBuf::from("/System"),
PathBuf::from("/usr/local/bin"),
],
"linux" => vec![
PathBuf::from("/etc"),
PathBuf::from("/bin"),
PathBuf::from("/sbin"),
],
"bellandeos" => vec![
PathBuf::from("/bell/etc"),
PathBuf::from("/bell/bin"),
PathBuf::from("/bell/security"),
],
_ => vec![],
}
}
/// Performs a comprehensive security audit of the system
pub async fn perform_security_audit(config: &Config) -> Result<()> {
let audit_config = SecurityAuditConfig::default();
info!("Starting security audit for {}", std::env::consts::OS);
// Check for system updates
check_system_updates().await?;
// Scan for vulnerabilities
scan_for_vulnerabilities(&audit_config).await?;
// Check for suspicious processes
check_suspicious_processes(&audit_config).await?;
// Check for unauthorized users
check_unauthorized_users(config).await?;
// Check for open ports
check_open_ports(&audit_config).await?;
// Check file integrity
check_file_integrity(&audit_config).await?;
// OS-specific security checks
perform_os_specific_checks().await?;
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
&format!("Completed security audit on {}", std::env::consts::OS),
)
.await?;
Ok(())
}
async fn check_system_updates() -> Result<()> {
match std::env::consts::OS {
"macos" => {
let output = Command::new("softwareupdate")
.arg("--list")
.output()
.context("Failed to check for macOS updates")?;
if !output.stdout.is_empty() {
log_audit_event("SECURITY_AUDIT", "SYSTEM", "macOS updates available").await?;
}
}
"linux" => {
let output = Command::new("apt")
.args(&["list", "--upgradable"])
.output()
.context("Failed to check for Linux updates")?;
if !output.stdout.is_empty() {
log_audit_event("SECURITY_AUDIT", "SYSTEM", "Linux updates available").await?;
}
}
"bellandeos" => {
let output = Command::new("bellctl")
.args(&["update", "check"])
.output()
.context("Failed to check for BellandeOS updates")?;
if !output.stdout.is_empty() {
log_audit_event("SECURITY_AUDIT", "SYSTEM", "BellandeOS updates available").await?;
}
}
_ => anyhow::bail!("Unsupported operating system"),
}
Ok(())
}
async fn scan_for_vulnerabilities(config: &SecurityAuditConfig) -> Result<()> {
log_audit_event("SECURITY_AUDIT", "SYSTEM", "Starting vulnerability scan").await?;
// Check for known vulnerable software versions
check_software_versions().await?;
// Check for common misconfigurations
check_common_misconfigurations(config).await?;
// Check for weak permissions
check_permissions(config).await?;
log_audit_event("SECURITY_AUDIT", "SYSTEM", "Vulnerability scan completed").await?;
Ok(())
}
async fn check_software_versions() -> Result<()> {
// Check OpenSSL version
let openssl_version = Command::new("openssl")
.arg("version")
.output()
.context("Failed to check OpenSSL version")?;
if !openssl_version.status.success() {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
"Warning: Unable to verify OpenSSL version",
)
.await?;
}
Ok(())
}
async fn check_common_misconfigurations(config: &SecurityAuditConfig) -> Result<()> {
for path in &config.critical_paths {
check_path_permissions(path).await?;
}
// Check world-writable files
check_world_writable_files().await?;
// Check for dangerous SUID/SGID binaries
check_suid_binaries().await?;
Ok(())
}
async fn check_permissions(config: &SecurityAuditConfig) -> Result<()> {
for path in &config.critical_paths {
let metadata = fs::metadata(path).context("Failed to get path metadata")?;
let mode = metadata.permissions().mode();
if mode & 0o777 > 0o755 {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
&format!("Excessive permissions found on: {:?}", path),
)
.await?;
}
}
Ok(())
}
async fn check_path_permissions(path: &Path) -> Result<()> {
let metadata = fs::metadata(path).context("Failed to get path metadata")?;
let mode = metadata.permissions().mode();
// Check for excessive permissions
if mode & 0o777 > 0o755 {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
&format!(
"Warning: Excessive permissions ({:o}) on path: {:?}",
mode & 0o777,
path
),
)
.await?;
}
// Check owner/group
if mode & 0o7000 != 0 {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
&format!(
"Warning: Special bits ({:o}) set on path: {:?}",
mode & 0o7000,
path
),
)
.await?;
}
Ok(())
}
async fn check_world_writable_files() -> Result<()> {
let critical_directories = match std::env::consts::OS {
"macos" => vec!["/etc", "/usr", "/bin", "/sbin", "/System"],
"linux" => vec!["/etc", "/usr", "/bin", "/sbin", "/lib", "/boot"],
"bellandeos" => vec!["/bell/etc", "/bell/bin", "/bell/lib", "/bell/security"],
_ => vec![],
};
for dir in critical_directories {
for entry in WalkDir::new(dir)
.follow_links(false)
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry.path();
if let Ok(metadata) = fs::metadata(path) {
let mode = metadata.permissions().mode();
// Check for world-writable permissions (others write permission)
if mode & 0o002 != 0 {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
&format!("Warning: World-writable file found: {:?}", path),
)
.await?;
}
}
}
}
Ok(())
}
async fn check_suid_binaries() -> Result<()> {
let critical_directories = match std::env::consts::OS {
"macos" => vec!["/usr/bin", "/usr/sbin", "/usr/local/bin"],
"linux" => vec!["/usr/bin", "/usr/sbin", "/usr/local/bin", "/usr/local/sbin"],
"bellandeos" => vec!["/bell/bin", "/bell/sbin", "/bell/local/bin"],
_ => vec![],
};
// Known safe SUID binaries
let safe_suid_binaries = HashSet::from([
"ping",
"su",
"sudo",
"passwd",
"mount",
"umount",
"fusermount",
"newgrp",
"chsh",
"gpasswd",
]);
for dir in critical_directories {
for entry in WalkDir::new(dir)
.follow_links(false)
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry.path();
if let Ok(metadata) = fs::metadata(path) {
let mode = metadata.permissions().mode();
// Check for SUID/SGID bits
if mode & 0o6000 != 0 {
// Get binary name
let binary_name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown");
// If it's not in our safe list, log it
if !safe_suid_binaries.contains(binary_name) {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
&format!(
"Warning: SUID/SGID binary found: {:?} (mode: {:o})",
path,
mode & 0o7777
),
)
.await?;
}
}
}
}
}
Ok(())
}
async fn check_suspicious_processes(config: &SecurityAuditConfig) -> Result<()> {
let system = System::new_all();
for (pid, process) in system.processes() {
let process_name = process.name().to_lowercase();
for pattern in &config.suspicious_process_patterns {
if process_name.contains(pattern) {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
&format!("Suspicious process found: {} (PID: {})", process_name, pid),
)
.await?;
// Additional process information
if let Some(cmd) = process.cmd().first() {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
&format!("Process command: {}", cmd),
)
.await?;
}
}
}
}
Ok(())
}
async fn check_unauthorized_users(config: &Config) -> Result<()> {
match std::env::consts::OS {
"macos" => check_macos_users(config).await?,
"linux" => check_linux_users(config).await?,
"bellandeos" => check_bellande_users(config).await?,
_ => anyhow::bail!("Unsupported operating system"),
}
Ok(())
}
async fn check_macos_users(config: &Config) -> Result<()> {
let output = Command::new("dscl")
.args(&[".", "list", "/Users"])
.output()
.context("Failed to list macOS users")?;
let users = String::from_utf8_lossy(&output.stdout);
for user in users.lines() {
if !config.users.iter().any(|u| u.username == user) && !is_macos_system_user(user) {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
&format!("Unauthorized macOS user found: {}", user),
)
.await?;
}
}
Ok(())
}
async fn check_linux_users(config: &Config) -> Result<()> {
let passwd = fs::read_to_string("/etc/passwd").context("Failed to read /etc/passwd")?;
for line in passwd.lines() {
let username = line.split(':').next().unwrap_or("");
if !config.users.iter().any(|u| u.username == username) && !is_linux_system_user(username) {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
&format!("Unauthorized Linux user found: {}", username),
)
.await?;
}
}
Ok(())
}
async fn check_bellande_users(config: &Config) -> Result<()> {
let output = Command::new("bellctl")
.args(&["user", "list"])
.output()
.context("Failed to list BellandeOS users")?;
let users = String::from_utf8_lossy(&output.stdout);
for user in users.lines() {
if !config.users.iter().any(|u| u.username == user) && !is_bellande_system_user(user) {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
&format!("Unauthorized BellandeOS user found: {}", user),
)
.await?;
}
}
Ok(())
}
fn is_macos_system_user(username: &str) -> bool {
matches!(
username,
"_spotlight" | "_locationd" | "_mdnsresponder" | "root" | "daemon"
)
}
fn is_linux_system_user(username: &str) -> bool {
matches!(
username,
"root"
| "daemon"
| "bin"
| "sys"
| "sync"
| "games"
| "man"
| "lp"
| "mail"
| "news"
| "uucp"
| "proxy"
| "www-data"
| "backup"
| "list"
| "irc"
| "gnats"
| "nobody"
| "systemd-network"
| "systemd-resolve"
| "systemd-timesync"
| "messagebus"
| "syslog"
| "avahi"
| "_apt"
| "sshd"
)
}
fn is_bellande_system_user(username: &str) -> bool {
matches!(
username,
"bellroot" | "bellsys" | "bellservice" | "bellnetwork" | "bellsecurity"
)
}
async fn check_open_ports(config: &SecurityAuditConfig) -> Result<()> {
match std::env::consts::OS {
"macos" => {
let output = Command::new("lsof")
.args(&["-i", "-P", "-n"])
.output()
.context("Failed to check macOS open ports")?;
check_port_output(
&String::from_utf8_lossy(&output.stdout),
&config.allowed_ports,
)
.await?;
}
"linux" => {
let output = Command::new("netstat")
.args(&["-tuln"])
.output()
.context("Failed to check Linux open ports")?;
check_port_output(
&String::from_utf8_lossy(&output.stdout),
&config.allowed_ports,
)
.await?;
}
"bellandeos" => {
let output = Command::new("bellctl")
.args(&["network", "ports"])
.output()
.context("Failed to check BellandeOS open ports")?;
check_port_output(
&String::from_utf8_lossy(&output.stdout),
&config.allowed_ports,
)
.await?;
}
_ => anyhow::bail!("Unsupported operating system"),
}
Ok(())
}
async fn check_port_output(output: &str, allowed_ports: &HashSet<u16>) -> Result<()> {
for line in output.lines() {
if line.contains("LISTEN") {
let port = extract_port_from_line(line);
if let Some(port) = port {
if !allowed_ports.contains(&port) {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
&format!("Unauthorized open port found: {}", port),
)
.await?;
}
}
}
}
Ok(())
}
fn extract_port_from_line(line: &str) -> Option<u16> {
line.split(':')
.last()?
.split_whitespace()
.next()?
.parse()
.ok()
}
async fn check_file_integrity(config: &SecurityAuditConfig) -> Result<()> {
// Initialize or load hash database
let mut hash_database = load_hash_database(&config.file_hash_database)?;
for path in &config.critical_paths {
check_directory_integrity(path, &mut hash_database).await?;
}
// Save updated hashes
save_hash_database(&config.file_hash_database, &hash_database)?;
Ok(())
}
async fn check_directory_integrity(
path: &Path,
hash_database: &mut HashMap<PathBuf, String>,
) -> Result<()> {
if path.is_dir() {
for entry in fs::read_dir(path)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
let current_hash = calculate_file_hash(&path)?;
if let Some(stored_hash) = hash_database.get(&path) {
if stored_hash != &current_hash {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
&format!("File integrity mismatch: {:?}", path),
)
.await?;
}
}
// Update hash in database
hash_database.insert(path, current_hash);
} else if path.is_dir() {
Box::pin(check_directory_integrity(&path, hash_database)).await?;
}
}
}
Ok(())
}
async fn perform_os_specific_checks() -> Result<()> {
match std::env::consts::OS {
"macos" => perform_macos_specific_checks().await?,
"linux" => perform_linux_specific_checks().await?,
"bellandeos" => perform_bellande_specific_checks().await?,
_ => anyhow::bail!("Unsupported operating system"),
}
Ok(())
}
async fn perform_macos_specific_checks() -> Result<()> {
// Check System Integrity Protection (SIP)
let sip_status = Command::new("csrutil")
.arg("status")
.output()
.context("Failed to check SIP status")?;
if !String::from_utf8_lossy(&sip_status.stdout).contains("enabled") {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
"Warning: System Integrity Protection is disabled",
)
.await?;
}
// Check FileVault status
let filevault_status = Command::new("fdesetup")
.arg("status")
.output()
.context("Failed to check FileVault status")?;
if !String::from_utf8_lossy(&filevault_status.stdout).contains("On") {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
"Warning: FileVault is not enabled",
)
.await?;
}
// Check Gatekeeper status
let gatekeeper_status = Command::new("spctl")
.args(&["--status"])
.output()
.context("Failed to check Gatekeeper status")?;
if !String::from_utf8_lossy(&gatekeeper_status.stdout).contains("enabled") {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
"Warning: Gatekeeper is disabled",
)
.await?;
}
Ok(())
}
async fn perform_linux_specific_checks() -> Result<()> {
// Check SELinux status
if Path::new("/etc/selinux/config").exists() {
let selinux_status = Command::new("getenforce")
.output()
.context("Failed to check SELinux status")?;
if !String::from_utf8_lossy(&selinux_status.stdout).contains("Enforcing") {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
"Warning: SELinux is not in enforcing mode",
)
.await?;
}
}
// Check AppArmor status
if Path::new("/etc/apparmor").exists() {
let apparmor_status = Command::new("aa-status")
.output()
.context("Failed to check AppArmor status")?;
if !apparmor_status.status.success() {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
"Warning: AppArmor is not properly configured",
)
.await?;
}
}
// Check kernel parameters
check_kernel_parameters().await?;
Ok(())
}
async fn perform_bellande_specific_checks() -> Result<()> {
// Check BellandeOS security module status
let security_status = Command::new("bellctl")
.args(&["security", "status"])
.output()
.context("Failed to check BellandeOS security status")?;
if !String::from_utf8_lossy(&security_status.stdout).contains("enabled") {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
"Warning: BellandeOS security module is not enabled",
)
.await?;
}
// Check BellandeOS integrity
let integrity_check = Command::new("bellctl")
.args(&["verify", "system"])
.output()
.context("Failed to verify BellandeOS integrity")?;
if !integrity_check.status.success() {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
"Warning: BellandeOS system integrity check failed",
)
.await?;
}
// Check BellandeOS update status
let update_status = Command::new("bellctl")
.args(&["update", "status"])
.output()
.context("Failed to check BellandeOS update status")?;
if !update_status.status.success() {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
"Warning: BellandeOS update check failed",
)
.await?;
}
Ok(())
}
async fn check_kernel_parameters() -> Result<()> {
let critical_params = [
"kernel.randomize_va_space",
"kernel.kptr_restrict",
"kernel.dmesg_restrict",
"kernel.perf_event_paranoid",
"net.ipv4.tcp_syncookies",
];
for param in &critical_params {
let output = Command::new("sysctl")
.arg(param)
.output()
.context(format!("Failed to check kernel parameter: {}", param))?;
if !output.status.success() {
log_audit_event(
"SECURITY_AUDIT",
"SYSTEM",
&format!("Warning: Failed to verify kernel parameter: {}", param),
)
.await?;
}
}
Ok(())
}
fn calculate_file_hash(path: &Path) -> Result<String> {
let mut file = File::open(path)?;
let mut hasher = Sha256::new();
std::io::copy(&mut file, &mut hasher)?;
Ok(format!("{:x}", hasher.finalize()))
}
fn load_hash_database(path: &Path) -> Result<HashMap<PathBuf, String>> {
if path.exists() {
let file = File::open(path)?;
let reader = BufReader::new(file);
Ok(serde_json::from_reader(reader)?)
} else {
Ok(HashMap::new())
}
}
fn save_hash_database(path: &Path, database: &HashMap<PathBuf, String>) -> Result<()> {
let file = File::create(path)?;
serde_json::to_writer_pretty(file, database)?;
Ok(())
}

View File

@@ -0,0 +1,123 @@
// Copyright (C) 2024 Bellande Architecture Mechanism Research Innovation Center, Ronaldson Bellande
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::audit::audit::log_audit_event;
use crate::config::config::Config;
use crate::user_privilege::user::User;
use anyhow::{Context, Result};
use argon2;
use argon2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
use argon2::Argon2;
use rand_core::OsRng;
use std::collections::HashMap;
use std::time::{Duration, Instant, SystemTime};
use totp_rs::TOTP;
pub struct Session {
pub user: User,
pub expiry: SystemTime,
}
pub struct RateLimiter {
attempts: HashMap<String, Vec<Instant>>,
max_attempts: usize,
window: Duration,
}
impl RateLimiter {
pub fn new(max_attempts: usize, window: Duration) -> Self {
RateLimiter {
attempts: HashMap::new(),
max_attempts,
window,
}
}
pub fn check(&mut self, key: &str) -> bool {
let now = Instant::now();
let attempts = self
.attempts
.entry(key.to_string())
.or_insert_with(Vec::new);
attempts.retain(|&t| now.duration_since(t) < self.window);
if attempts.len() >= self.max_attempts {
false
} else {
attempts.push(now);
true
}
}
}
pub async fn authenticate_user(
config: &Config,
username: &str,
password: &str,
totp_code: &str,
rate_limiter: &mut RateLimiter,
) -> Result<Option<User>> {
if !rate_limiter.check(username) {
log_audit_event("AUTHENTICATION_RATE_LIMIT", username, "Rate limit exceeded").await?;
return Ok(None);
}
if let Some(user) = config.users.iter().find(|u| u.username == username) {
if verify_password(&user.password_hash, password)? {
let totp = TOTP::new(
totp_rs::Algorithm::SHA1,
6,
1,
30,
user.totp_secret.as_bytes().to_vec(),
)
.context("Failed to create TOTP")?;
if totp.check_current(totp_code)? {
log_audit_event(
"AUTHENTICATION_SUCCESS",
&user.username,
"User authenticated successfully",
)
.await?;
return Ok(Some(user.clone()));
}
}
}
log_audit_event("AUTHENTICATION_FAILURE", username, "Authentication failed").await?;
Ok(None)
}
fn verify_password(hash: &str, password: &str) -> Result<bool> {
let parsed_hash = PasswordHash::new(hash).context("Failed to parse password hash")?;
Ok(Argon2::default()
.verify_password(password.as_bytes(), &parsed_hash)
.is_ok())
}
fn hash_password(password: &str) -> Result<String> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let password_hash = argon2
.hash_password(password.as_bytes(), &salt)
.context("Failed to hash password")?
.to_string();
Ok(password_hash)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
pub mod authentication;
pub mod complication;

213
src/bell.rs Normal file
View File

@@ -0,0 +1,213 @@
// Copyright (C) 2024 Bellande Architecture Mechanism Research Innovation Center, Ronaldson Bellande
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
mod audit;
mod authentication_compliance;
mod command;
mod config;
mod hsm;
mod network;
mod user_privilege;
use std::time::{Duration, SystemTime};
use structopt::StructOpt;
use tokio;
use crate::authentication_compliance::authentication::{authenticate_user, RateLimiter, Session};
use crate::command::command::run_command_with_privilege;
use crate::config::config::Config;
use crate::user_privilege::privilege::{PrivilegeConfig, PrivilegeLevel, PrivilegeManager};
use crate::user_privilege::user::{
add_user, add_user_to_group, change_password, change_privilege, remove_user,
remove_user_from_group,
};
#[derive(StructOpt, Debug)]
#[structopt(name = "bell", about = "Privilege escalation system")]
enum Opt {
#[structopt(name = "run")]
Run {
#[structopt(short, long)]
privilege_level: String,
#[structopt(short, long)]
command: String,
#[structopt(short, long)]
args: Vec<String>,
},
#[structopt(name = "user")]
User {
#[structopt(subcommand)]
cmd: UserCommand,
},
}
#[derive(StructOpt, Debug)]
enum UserCommand {
Add {
username: String,
#[structopt(short, long)]
privilege: String,
},
Remove {
username: String,
},
ChangePassword {
username: String,
},
ChangePrivilege {
username: String,
privilege: String,
},
AddToGroup {
username: String,
group: String,
},
RemoveFromGroup {
username: String,
group: String,
},
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize logging
log4rs::init_file("log4rs.yaml", Default::default())?;
let opt = Opt::from_args();
let mut config = Config::load()?;
let privilege_config = PrivilegeConfig::default();
let mut rate_limiter = RateLimiter::new(5, Duration::from_secs(60));
let privilege_manager = PrivilegeManager::new(privilege_config);
match opt {
Opt::Run {
privilege_level,
command,
args,
} => {
println!("Enter username:");
let mut username = String::new();
std::io::stdin().read_line(&mut username)?;
let username = username.trim();
println!("Enter password:");
let mut password = String::new();
std::io::stdin().read_line(&mut password)?;
let password = password.trim();
println!("Enter TOTP code:");
let mut totp_code = String::new();
std::io::stdin().read_line(&mut totp_code)?;
let totp_code = totp_code.trim();
if let Some(user) =
authenticate_user(&config, username, password, totp_code, &mut rate_limiter).await?
{
let session = Session {
user: user.clone(),
expiry: SystemTime::now() + Duration::from_secs(config.session_duration),
};
let privilege_level = match privilege_level.as_str() {
"bell" => PrivilegeLevel::Bell,
"root" => PrivilegeLevel::Root,
"admin" => PrivilegeLevel::Administrator,
"user" => PrivilegeLevel::User,
_ => {
println!(
"Invalid privilege level. Use 'bell', 'root', 'admin', or 'user'."
);
return Ok(());
}
};
run_command_with_privilege(
&session,
&command,
&args,
privilege_level,
&config,
&privilege_manager,
)
.await?;
} else {
println!("Authentication failed.");
}
}
Opt::User { cmd } => match cmd {
UserCommand::Add {
username,
privilege,
} => {
println!("Enter new password:");
let mut password = String::new();
std::io::stdin().read_line(&mut password)?;
let password = password.trim();
let privilege_level = match privilege.as_str() {
"bell" => PrivilegeLevel::Bell,
"root" => PrivilegeLevel::Root,
"admin" | "administrator" => PrivilegeLevel::Administrator,
"user" => PrivilegeLevel::User,
_ => {
println!(
"Invalid privilege level. Use 'bell', 'root', 'admin', or 'user'."
);
return Ok(());
}
};
add_user(&mut config, &username, password, privilege_level).await?;
}
UserCommand::Remove { username } => {
remove_user(&mut config, &username).await?;
}
UserCommand::ChangePassword { username } => {
println!("Enter new password:");
let mut password = String::new();
std::io::stdin().read_line(&mut password)?;
let password = password.trim();
change_password(&mut config, &username, password).await?;
}
UserCommand::ChangePrivilege {
username,
privilege,
} => {
let privilege_level = match privilege.as_str() {
"bell" => PrivilegeLevel::Bell,
"root" => PrivilegeLevel::Root,
"admin" | "administrator" => PrivilegeLevel::Administrator,
"user" => PrivilegeLevel::User,
_ => {
println!(
"Invalid privilege level. Use 'bell', 'root', 'admin', or 'user'."
);
return Ok(());
}
};
change_privilege(&mut config, &username, privilege_level).await?;
}
UserCommand::AddToGroup { username, group } => {
add_user_to_group(&mut config, &username, &group).await?;
}
UserCommand::RemoveFromGroup { username, group } => {
remove_user_from_group(&mut config, &username, &group).await?;
}
},
}
Ok(())
}

693
src/command/command.rs Normal file
View File

@@ -0,0 +1,693 @@
// Copyright (C) 2024 Bellande Architecture Mechanism Research Innovation Center, Ronaldson Bellande
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::collections::HashSet;
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use tokio::process::Command as TokioCommand;
use anyhow::{Context, Result};
use nix::unistd::{Gid, Uid};
use serde::{Deserialize, Serialize};
use syscallz::{Context as SyscallContext, Syscall};
use tokio::time::timeout;
use crate::audit::audit::log_audit_event;
use crate::authentication_compliance::authentication::Session;
use crate::config::config::Config;
use crate::network::network::{is_network_allowed, isolate_network, restore_network};
use crate::user_privilege::privilege::{PrivilegeLevel, PrivilegeManager};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandConfig {
dangerous_patterns: HashSet<String>,
allowed_paths: Vec<PathBuf>,
max_execution_time: Duration,
sandbox_enabled: bool,
network_isolation_required: bool,
max_output_size: usize,
log_output: bool,
}
impl Default for CommandConfig {
fn default() -> Self {
let allowed_paths = match std::env::consts::OS {
"macos" => vec![
PathBuf::from("/usr/bin"),
PathBuf::from("/usr/local/bin"),
PathBuf::from("/opt/homebrew/bin"),
],
"linux" => vec![
PathBuf::from("/usr/bin"),
PathBuf::from("/usr/local/bin"),
PathBuf::from("/bin"),
],
"bellandeos" => vec![
PathBuf::from("/bell/bin"),
PathBuf::from("/bell/usr/bin"),
PathBuf::from("/bell/local/bin"),
],
_ => vec![],
};
let mut dangerous_patterns = HashSet::new();
dangerous_patterns.insert("rm -rf /*".to_string());
dangerous_patterns.insert("chmod 777".to_string());
dangerous_patterns.insert("dd if=/dev/zero".to_string());
dangerous_patterns.insert("mkfs".to_string());
dangerous_patterns.insert("> /dev/sda".to_string());
dangerous_patterns.insert(":(){ :|:& };:".to_string()); // Fork bomb
dangerous_patterns.insert("sudo rm".to_string());
dangerous_patterns.insert("> /dev/null".to_string());
CommandConfig {
dangerous_patterns,
allowed_paths,
max_execution_time: Duration::from_secs(300),
sandbox_enabled: true,
network_isolation_required: true,
max_output_size: 1024 * 1024,
log_output: true,
}
}
}
// Create a wrapper that implements Debug
struct SandboxContext {
inner: SyscallContext,
}
impl std::fmt::Debug for SandboxContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SandboxContext")
.field("inner", &"SyscallContext")
.finish()
}
}
impl SandboxContext {
fn new(context: SyscallContext) -> Self {
Self { inner: context }
}
fn load(&self) -> Result<()> {
self.inner
.load()
.map_err(|e| anyhow::anyhow!("Failed to load sandbox: {}", e))
}
fn allow_syscall(&mut self, syscall: Syscall) -> Result<()> {
self.inner
.allow_syscall(syscall)
.map_err(|e| anyhow::anyhow!("Failed to allow syscall: {}", e))
}
}
#[derive(Debug)]
struct CommandContext {
command: String,
args: Vec<String>,
privilege_level: PrivilegeLevel,
username: String,
start_time: SystemTime,
sandbox: Option<SandboxContext>,
config: CommandConfig,
}
#[derive(Debug, Clone)]
struct DangerousPattern {
pattern: String,
description: String,
}
impl From<(&str, &str)> for DangerousPattern {
fn from((pattern, description): (&str, &str)) -> Self {
DangerousPattern {
pattern: pattern.to_string(),
description: description.to_string(),
}
}
}
// Implementation for command validation and execution
pub async fn run_command_with_privilege(
session: &Session,
command: &str,
args: &[String],
required_privilege: PrivilegeLevel,
config: &Config,
privilege_manager: &PrivilegeManager,
) -> Result<()> {
let cmd_config = CommandConfig::default();
// Validate input
validate_command_input(command, args)?;
// Check privileges and session
check_session_and_permissions(
session,
command,
args,
required_privilege,
config,
privilege_manager,
&cmd_config, // Add the command config parameter
)
.await?;
// Create and execute command context
let ctx = create_command_context(command, args, required_privilege, session, &cmd_config)?;
execute_command_safely(ctx).await
}
fn validate_command_input(command: &str, args: &[String]) -> Result<()> {
if command.is_empty() {
return Err(anyhow::anyhow!("Command cannot be empty"));
}
// Check for null bytes and other dangerous characters
if command.contains('\0') || args.iter().any(|arg| arg.contains('\0')) {
return Err(anyhow::anyhow!("Command contains invalid characters"));
}
// Validate command path
let command_path = PathBuf::from(command);
if !command_path.is_absolute() {
return Err(anyhow::anyhow!("Command must use absolute path"));
}
Ok(())
}
async fn check_session_and_permissions(
session: &Session,
command: &str,
args: &[String],
required_privilege: PrivilegeLevel,
config: &Config,
privilege_manager: &PrivilegeManager,
cmd_config: &CommandConfig,
) -> Result<()> {
// Check session state
check_session_state(session, command, args).await?;
// Check permissions
check_command_permissions(
session,
command,
args,
required_privilege,
config,
privilege_manager,
)
.await?;
// Check network access if required by command config
if cmd_config.network_isolation_required {
check_network_access(config, session, command, args).await?;
}
Ok(())
}
async fn check_session_state(session: &Session, command: &str, args: &[String]) -> Result<()> {
// Check session expiry
if SystemTime::now() > session.expiry {
log_audit_event(
"SESSION_EXPIRED",
&session.user.username,
&format!(
"Attempted to run command with expired session: {} {:?}",
command, args
),
)
.await?;
return Err(anyhow::anyhow!(
"Session expired. Please authenticate again."
));
}
// Check that user exists and is valid
if session.user.username.is_empty() {
log_audit_event(
"INVALID_SESSION",
"unknown",
&format!(
"Attempted to run command without valid user: {} {:?}",
command, args
),
)
.await?;
return Err(anyhow::anyhow!("Invalid session: no user associated"));
}
Ok(())
}
async fn check_command_permissions(
session: &Session,
command: &str,
args: &[String],
required_privilege: PrivilegeLevel,
config: &Config,
privilege_manager: &PrivilegeManager,
) -> Result<bool> {
// Check base user privileges
if !privilege_manager
.check_permission(&session.user, required_privilege, config)
.await?
{
log_audit_event(
"PERMISSION_DENIED",
&session.user.username,
&format!(
"Insufficient privileges for command: {} {:?}, required: {:?}",
command, args, required_privilege
),
)
.await?;
return Ok(false); // Return Ok(false) instead of Err
}
// Check if user belongs to required groups
let has_required_group = session.user.groups.iter().any(|group| {
config
.groups
.iter()
.any(|g| &g.name == group && g.permissions.contains(&required_privilege.to_string()))
});
if !has_required_group && required_privilege > session.user.privilege {
log_audit_event(
"GROUP_PERMISSION_DENIED",
&session.user.username,
&format!(
"User lacks required group membership for command: {} {:?}",
command, args
),
)
.await?;
return Ok(false); // Return Ok(false) instead of Err
}
Ok(true) // Return Ok(true) if all checks pass
}
async fn check_network_access(
config: &Config,
session: &Session,
command: &str,
args: &[String],
) -> Result<bool> {
// Use is_network_allowed directly with the config
if !is_network_allowed(config).await? {
log_audit_event(
"NETWORK_DENIED",
&session.user.username,
&format!("Network access denied for: {} {:?}", command, args),
)
.await?;
return Ok(false);
}
Ok(true)
}
async fn execute_command_safely(ctx: CommandContext) -> Result<()> {
// Log command execution start
log_audit_event(
"COMMAND_START",
&ctx.username,
&format!("Executing: {} {:?}", ctx.command, ctx.args),
)
.await?;
// Check for dangerous patterns
check_dangerous_patterns(&ctx).await?;
// Apply sandbox if enabled
if let Some(ref sandbox) = ctx.sandbox {
sandbox.load().context("Failed to load sandbox")?;
}
// Drop privileges if necessary
if ctx.privilege_level != PrivilegeLevel::Bell {
drop_privileges().context("Failed to drop privileges")?;
}
// Isolate network if required
let network_isolated = if ctx.config.network_isolation_required {
isolate_network().await?;
true
} else {
false
};
// Execute command with timeout
let result = execute_command_with_timeout(&ctx).await;
// Restore network if it was isolated
if network_isolated {
restore_network().await?;
}
// Handle command result
match result {
Ok(output) => process_command_output(&ctx, &output).await?,
Err(e) => {
log_audit_event(
"COMMAND_FAILED",
&ctx.username,
&format!("Command failed: {} - Error: {}", ctx.command, e),
)
.await?;
return Err(e);
}
}
Ok(())
}
fn create_command_context(
command: &str,
args: &[String],
privilege_level: PrivilegeLevel,
session: &Session,
cmd_config: &CommandConfig,
) -> Result<CommandContext> {
Ok(CommandContext {
command: command.to_string(),
args: args.to_vec(),
privilege_level,
username: session.user.username.clone(),
start_time: SystemTime::now(),
sandbox: if cmd_config.sandbox_enabled {
Some(create_sandbox()?)
} else {
None
},
config: cmd_config.clone(),
})
}
async fn execute_command_with_timeout(ctx: &CommandContext) -> Result<std::process::Output> {
// Create tokio command
let mut command = TokioCommand::new(&ctx.command);
command.args(&ctx.args);
// Run with timeout
let output = timeout(ctx.config.max_execution_time, command.output())
.await
.context("Command execution timed out")?
.context("Command execution failed")?;
Ok(output)
}
async fn process_command_output(ctx: &CommandContext, output: &std::process::Output) -> Result<()> {
// Check output size limits
if output.stdout.len() > ctx.config.max_output_size
|| output.stderr.len() > ctx.config.max_output_size
{
log_audit_event(
"COMMAND_OUTPUT_TOO_LARGE",
&ctx.username,
&format!(
"Output size exceeds limit of {} bytes",
ctx.config.max_output_size
),
)
.await?;
return Err(anyhow::anyhow!("Command output exceeds size limit"));
}
// Process stderr if present
if !output.stderr.is_empty() {
let stderr = String::from_utf8_lossy(&output.stderr);
log_audit_event(
"COMMAND_ERROR",
&ctx.username,
&format!("Command produced error output: {}", stderr),
)
.await?;
}
// Process stdout if logging is enabled
if ctx.config.log_output && !output.stdout.is_empty() {
let stdout = String::from_utf8_lossy(&output.stdout);
log_audit_event(
"COMMAND_OUTPUT",
&ctx.username,
&format!("Command output: {}", stdout),
)
.await?;
}
// Check exit status
if !output.status.success() {
return Err(anyhow::anyhow!(
"Command failed with exit code: {}",
output.status.code().unwrap_or(-1)
));
}
Ok(())
}
fn create_sandbox() -> Result<SandboxContext> {
let mut ctx = SyscallContext::init()?;
use syscallz::Syscall;
// Essential system calls
let essential_syscalls = [
Syscall::read,
Syscall::write,
Syscall::exit,
Syscall::exit_group,
Syscall::brk,
Syscall::arch_prctl,
];
// File operations
let file_syscalls = [
Syscall::open,
Syscall::openat,
Syscall::close,
Syscall::access,
Syscall::getcwd,
Syscall::lseek,
Syscall::stat,
Syscall::fstat,
Syscall::lstat,
Syscall::readlink,
];
// Memory management
let memory_syscalls = [
Syscall::mmap,
Syscall::munmap,
Syscall::mprotect,
Syscall::mremap,
];
// Process management
let process_syscalls = [
Syscall::clone,
Syscall::fork,
Syscall::execve,
Syscall::kill,
Syscall::wait4,
Syscall::getpid,
Syscall::getppid,
Syscall::getuid,
Syscall::geteuid,
];
// Allow the syscalls
for syscall in essential_syscalls
.iter()
.chain(file_syscalls.iter())
.chain(memory_syscalls.iter())
.chain(process_syscalls.iter())
{
ctx.allow_syscall(*syscall)
.with_context(|| format!("Failed to add syscall rule: {:?}", syscall))?;
}
Ok(SandboxContext::new(ctx))
}
fn drop_privileges() -> Result<()> {
let nobody_uid = Uid::from_raw(65534); // nobody user
let nobody_gid = Gid::from_raw(65534); // nobody group
// Clear supplementary groups first
nix::unistd::setgroups(&[]).context("Failed to clear supplementary groups")?;
// Drop group privileges
nix::unistd::setresgid(nobody_gid, nobody_gid, nobody_gid)
.context("Failed to drop group privileges")?;
// Drop user privileges
nix::unistd::setresuid(nobody_uid, nobody_uid, nobody_uid)
.context("Failed to drop user privileges")?;
Ok(())
}
async fn check_dangerous_patterns(ctx: &CommandContext) -> Result<()> {
let full_command = format!("{} {}", ctx.command, ctx.args.join(" "));
// Check against dangerous patterns
for pattern in &ctx.config.dangerous_patterns {
if full_command.contains(pattern) {
log_audit_event(
"DANGEROUS_COMMAND",
&ctx.username,
&format!("Dangerous pattern detected: {}", pattern),
)
.await?;
return Err(anyhow::anyhow!("Dangerous command pattern detected"));
}
}
// OS-specific pattern checks
match std::env::consts::OS {
"macos" => check_macos_specific_patterns(ctx, &full_command).await?,
"linux" => check_linux_specific_patterns(ctx, &full_command).await?,
"bellandeos" => check_bellande_specific_patterns(ctx, &full_command).await?,
_ => {}
}
Ok(())
}
fn convert_patterns<const N: usize>(patterns: [(&str, &str); N]) -> Vec<DangerousPattern> {
patterns.into_iter().map(DangerousPattern::from).collect()
}
async fn check_macos_specific_patterns(ctx: &CommandContext, command: &str) -> Result<()> {
let dangerous_patterns = convert_patterns([
("diskutil eraseDisk", "Disk erasure attempt"),
("csrutil disable", "SIP disable attempt"),
("nvram", "NVRAM modification attempt"),
("kextload", "Kernel extension loading attempt"),
("spctl --master-disable", "Gatekeeper disable attempt"),
]);
check_patterns(ctx, command, &dangerous_patterns).await
}
async fn check_linux_specific_patterns(ctx: &CommandContext, command: &str) -> Result<()> {
let dangerous_patterns = convert_patterns([
("modprobe", "Kernel module loading attempt"),
("insmod", "Kernel module insertion attempt"),
("mount", "File system mounting attempt"),
("sysctl -w", "Sysctl modification attempt"),
("echo 1 > /proc/sys", "Sysctl modification attempt"),
("iptables -F", "Firewall flush attempt"),
]);
check_patterns(ctx, command, &dangerous_patterns).await
}
async fn check_bellande_specific_patterns(ctx: &CommandContext, command: &str) -> Result<()> {
let dangerous_patterns = convert_patterns([
("bellctl system reset", "System reset attempt"),
("bellctl security disable", "Security disable attempt"),
("bellctl kernel modify", "Kernel modification attempt"),
("bellctl firewall disable", "Firewall disable attempt"),
("bellctl audit stop", "Audit stop attempt"),
]);
check_patterns(ctx, command, &dangerous_patterns).await
}
async fn check_patterns(
ctx: &CommandContext,
command: &str,
patterns: &[DangerousPattern],
) -> Result<()> {
for pattern in patterns {
if command.contains(&pattern.pattern) {
log_audit_event("DANGEROUS_COMMAND", &ctx.username, &pattern.description).await?;
return Err(anyhow::anyhow!("Dangerous command pattern detected"));
}
}
Ok(())
}
async fn check_macos_patterns(ctx: &CommandContext, command: &str) -> Result<()> {
let patterns = vec![
DangerousPattern {
pattern: "diskutil eraseDisk".to_string(),
description: "Disk erasure attempt".to_string(),
},
DangerousPattern {
pattern: "csrutil disable".to_string(),
description: "SIP disable attempt".to_string(),
},
DangerousPattern {
pattern: "nvram".to_string(),
description: "NVRAM modification attempt".to_string(),
},
DangerousPattern {
pattern: "kextload".to_string(),
description: "Kernel extension loading attempt".to_string(),
},
];
check_patterns(ctx, command, &patterns).await
}
async fn check_linux_patterns(ctx: &CommandContext, command: &str) -> Result<()> {
let patterns = vec![
DangerousPattern {
pattern: "modprobe".to_string(),
description: "Kernel module loading attempt".to_string(),
},
DangerousPattern {
pattern: "insmod".to_string(),
description: "Kernel module insertion attempt".to_string(),
},
DangerousPattern {
pattern: "mount".to_string(),
description: "File system mounting attempt".to_string(),
},
DangerousPattern {
pattern: "sysctl -w".to_string(),
description: "Sysctl modification attempt".to_string(),
},
];
check_patterns(ctx, command, &patterns).await
}
async fn check_bellande_patterns(ctx: &CommandContext, command: &str) -> Result<()> {
let patterns = vec![
DangerousPattern {
pattern: "bellctl system reset".to_string(),
description: "System reset attempt".to_string(),
},
DangerousPattern {
pattern: "bellctl security disable".to_string(),
description: "Security disable attempt".to_string(),
},
DangerousPattern {
pattern: "bellctl kernel modify".to_string(),
description: "Kernel modification attempt".to_string(),
},
];
check_patterns(ctx, command, &patterns).await
}

1
src/command/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod command;

334
src/config/config.rs Normal file
View File

@@ -0,0 +1,334 @@
// Copyright (C) 2024 Bellande Architecture Mechanism Research Innovation Center, Ronaldson Bellande
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::collections::HashSet;
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::os::unix::fs::OpenOptionsExt;
use std::path::PathBuf;
use std::time::Duration;
use anyhow::{Context, Result};
use log::warn;
use serde::{Deserialize, Serialize};
use tokio::runtime::Runtime;
use crate::hsm::hsm::{decrypt_data, encrypt_data};
use crate::user_privilege::user::User;
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
pub users: Vec<User>,
pub groups: Vec<Group>,
pub session_duration: u64,
pub allowed_commands: Vec<String>,
pub denied_commands: Vec<String>,
pub allowed_networks: Vec<String>,
pub hsm_slot: u64,
pub hsm_pin: String,
pub security_settings: SecuritySettings,
pub os_specific: OsSpecificConfig,
pub paths: ConfigPaths,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Group {
pub name: String,
pub permissions: Vec<String>,
pub members: Vec<String>,
pub description: Option<String>,
pub created_at: chrono::DateTime<chrono::Utc>,
pub modified_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SecuritySettings {
pub password_min_length: usize,
pub password_require_special: bool,
pub password_require_numbers: bool,
pub password_require_uppercase: bool,
pub max_login_attempts: usize,
pub lockout_duration: Duration,
pub session_timeout: Duration,
pub mfa_required: bool,
pub allowed_ip_ranges: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct OsSpecificConfig {
pub macos: MacOSConfig,
pub linux: LinuxConfig,
pub bellandeos: BellandeOSConfig,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct MacOSConfig {
pub require_filevault: bool,
pub require_sip: bool,
pub allowed_applications: Vec<String>,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct LinuxConfig {
pub selinux_mode: String,
pub require_apparmor: bool,
pub kernel_hardening: bool,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct BellandeOSConfig {
pub security_level: String,
pub require_secure_boot: bool,
pub enable_kernel_protection: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ConfigPaths {
pub config_dir: PathBuf,
pub log_dir: PathBuf,
pub backup_dir: PathBuf,
}
impl Default for Config {
fn default() -> Self {
let os_paths = match std::env::consts::OS {
"macos" => ConfigPaths {
config_dir: PathBuf::from("/Library/Application Support/bell"),
log_dir: PathBuf::from("/var/log/bell"),
backup_dir: PathBuf::from("/var/backup/bell"),
},
"linux" => ConfigPaths {
config_dir: PathBuf::from("/etc/bell"),
log_dir: PathBuf::from("/var/log/bell"),
backup_dir: PathBuf::from("/var/backup/bell"),
},
"bellandeos" => ConfigPaths {
config_dir: PathBuf::from("/bell/etc/bell"),
log_dir: PathBuf::from("/bell/log/bell"),
backup_dir: PathBuf::from("/bell/backup/bell"),
},
_ => ConfigPaths {
config_dir: PathBuf::from("./config"),
log_dir: PathBuf::from("./log"),
backup_dir: PathBuf::from("./backup"),
},
};
Config {
users: Vec::new(),
groups: Vec::new(),
session_duration: 3600,
allowed_commands: get_default_allowed_commands(),
denied_commands: get_default_denied_commands(),
allowed_networks: vec!["127.0.0.1/8".to_string()],
hsm_slot: 0,
hsm_pin: String::new(),
security_settings: SecuritySettings {
password_min_length: 12,
password_require_special: true,
password_require_numbers: true,
password_require_uppercase: true,
max_login_attempts: 3,
lockout_duration: Duration::from_secs(300),
session_timeout: Duration::from_secs(3600),
mfa_required: true,
allowed_ip_ranges: vec!["192.168.0.0/16".to_string()],
},
os_specific: OsSpecificConfig {
macos: MacOSConfig {
require_filevault: true,
require_sip: true,
allowed_applications: vec![],
},
linux: LinuxConfig {
selinux_mode: "enforcing".to_string(),
require_apparmor: true,
kernel_hardening: true,
},
bellandeos: BellandeOSConfig {
security_level: "high".to_string(),
require_secure_boot: true,
enable_kernel_protection: true,
},
},
paths: os_paths,
}
}
}
impl Config {
pub fn load() -> Result<Self> {
let rt = Runtime::new()?;
rt.block_on(async {
let config_path = Self::get_config_path()?;
Self::ensure_directories_exist()?;
let encrypted_config =
fs::read_to_string(&config_path).context("Failed to read config file")?;
let decrypted_config = decrypt_data(&encrypted_config)
.await
.context("Failed to decrypt config file")?;
let mut config: Config =
toml::from_str(&decrypted_config).context("Failed to parse config file")?;
config.verify_integrity()?;
config.update_os_settings()?;
Ok(config)
})
}
pub fn save(&self) -> Result<()> {
let rt = Runtime::new()?;
rt.block_on(async {
self.verify_integrity()?;
self.create_backup().await?;
let config_str = toml::to_string(self).context("Failed to serialize config")?;
let encrypted_config = encrypt_data(&config_str)
.await
.context("Failed to encrypt config")?;
let config_path = Self::get_config_path()?;
let mut file = OpenOptions::new()
.write(true)
.create(true)
.mode(0o600)
.open(&config_path)
.context("Failed to open config file for writing")?;
file.write_all(encrypted_config.as_bytes())
.context("Failed to write config file")?;
Ok(())
})
}
fn get_config_path() -> Result<PathBuf> {
let config = Config::default();
let config_file = config.paths.config_dir.join("config.toml");
Ok(config_file)
}
fn ensure_directories_exist() -> Result<()> {
let config = Config::default();
fs::create_dir_all(&config.paths.config_dir)?;
fs::create_dir_all(&config.paths.log_dir)?;
fs::create_dir_all(&config.paths.backup_dir)?;
Ok(())
}
fn verify_integrity(&self) -> Result<()> {
if self.users.is_empty() {
warn!("No users defined in configuration");
}
for group in &self.groups {
for permission in &group.permissions {
if !is_valid_permission(permission) {
return Err(anyhow::anyhow!("Invalid permission: {}", permission));
}
}
}
let mut seen_users = HashSet::new();
for user in &self.users {
if !seen_users.insert(&user.username) {
return Err(anyhow::anyhow!("Duplicate user: {}", user.username));
}
}
Ok(())
}
async fn create_backup(&self) -> Result<()> {
let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
let backup_path = self
.paths
.backup_dir
.join(format!("config_backup_{}.toml", timestamp));
let config_str = toml::to_string(self)?;
let encrypted_backup = encrypt_data(&config_str).await?;
fs::write(backup_path, encrypted_backup)?;
Ok(())
}
fn update_os_settings(&mut self) -> Result<()> {
match std::env::consts::OS {
"macos" => {
self.os_specific.macos = MacOSConfig {
require_filevault: true,
require_sip: true,
allowed_applications: get_default_macos_applications(),
};
}
"linux" => {
self.os_specific.linux = LinuxConfig {
selinux_mode: "enforcing".to_string(),
require_apparmor: true,
kernel_hardening: true,
};
}
"bellandeos" => {
self.os_specific.bellandeos = BellandeOSConfig {
security_level: "high".to_string(),
require_secure_boot: true,
enable_kernel_protection: true,
};
}
_ => warn!("Unsupported operating system"),
}
Ok(())
}
}
fn get_default_allowed_commands() -> Vec<String> {
match std::env::consts::OS {
"macos" => vec!["ls".to_string(), "cd".to_string(), "pwd".to_string()],
"linux" => vec!["ls".to_string(), "cd".to_string(), "pwd".to_string()],
"bellandeos" => vec!["bellctl".to_string(), "ls".to_string(), "cd".to_string()],
_ => vec![],
}
}
fn get_default_denied_commands() -> Vec<String> {
match std::env::consts::OS {
"macos" => vec!["rm -rf /*".to_string(), "sudo su -".to_string()],
"linux" => vec!["rm -rf /*".to_string(), "dd".to_string()],
"bellandeos" => vec![
"bellctl system reset".to_string(),
"bellctl security disable".to_string(),
],
_ => vec![],
}
}
fn get_default_macos_applications() -> Vec<String> {
vec![
"/Applications/Terminal.app".to_string(),
"/Applications/Utilities/Terminal.app".to_string(),
]
}
fn is_valid_permission(permission: &str) -> bool {
matches!(
permission,
"read" | "write" | "execute" | "admin" | "system"
)
}

1
src/config/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod config;

820
src/hsm/hsm.rs Normal file
View File

@@ -0,0 +1,820 @@
// Copyright (C) 2024 Bellande Architecture Mechanism Research Innovation Center, Ronaldson Bellande
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::collections::HashMap;
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::SystemTime;
use aes_gcm::{
aead::{Aead, KeyInit},
Aes256Gcm, Key, Nonce,
};
use anyhow::{Context, Result};
use base64::{engine::general_purpose::STANDARD as base64, Engine as _};
use log::{info, warn};
use rand::{rngs::OsRng, RngCore};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use tokio::sync::RwLock;
const KEY_SIZE: usize = 32;
const NONCE_SIZE: usize = 12;
const KEY_ROTATION_DAYS: u64 = 30;
#[derive(Debug, Serialize, Deserialize)]
pub struct KeyStore {
label: String,
key_id: [u8; 8],
encrypted_key: Vec<u8>,
created_at: SystemTime,
rotated_at: Option<SystemTime>,
fingerprint: String,
metadata: KeyMetadata,
}
#[derive(Debug, Serialize, Deserialize)]
struct KeyMetadata {
algorithm: String,
key_type: KeyType,
usage: KeyUsage,
platform: String,
}
#[derive(Debug, Serialize, Deserialize)]
enum KeyType {
Master,
Data,
Signing,
Authentication,
}
#[derive(Debug, Serialize, Deserialize)]
enum KeyUsage {
Encryption,
Decryption,
Both,
}
pub struct SecureStorage {
key_store: Arc<RwLock<HashMap<String, KeyStore>>>,
storage_path: PathBuf,
}
// Platform-specific implementations
#[cfg(unix)]
fn set_secure_permissions(options: &mut OpenOptions) -> &mut OpenOptions {
use std::os::unix::fs::OpenOptionsExt;
options.mode(0o600)
}
fn encode_sensitive_data(data: &[u8]) -> String {
base64.encode(data)
}
fn decode_sensitive_data(data: &str) -> Result<Vec<u8>> {
base64
.decode(data.trim())
.context("Failed to decode base64 data")
}
impl SecureStorage {
pub async fn new() -> Result<Self> {
let storage_path = get_platform_storage_path()?;
ensure_secure_directory(&storage_path)?;
let key_store = Arc::new(RwLock::new(HashMap::new()));
let storage = SecureStorage {
key_store,
storage_path,
};
storage.initialize().await?;
Ok(storage)
}
async fn initialize(&self) -> Result<()> {
// Load existing keys
self.load_keys().await?;
// Check for key rotation
self.check_key_rotation().await?;
// Initialize platform-specific secure storage
match std::env::consts::OS {
"macos" => self.initialize_keychain().await?,
"linux" => self.initialize_keyring().await?,
"bellandeos" => self.initialize_bellande_secure_store().await?,
_ => warn!("No platform-specific secure storage available"),
}
Ok(())
}
async fn load_key_from_keyring(&self, line: &str) -> Result<Option<KeyStore>> {
let label = match line.split("bell_key_").nth(1) {
Some(l) => l,
None => return Ok(None),
};
let output = std::process::Command::new("keyctl")
.args(&["read", "user", &format!("bell_key_{}", label)])
.output()
.context("Failed to read key from keyring")?;
if output.status.success() {
let encoded_data = String::from_utf8_lossy(&output.stdout);
let key_data = decode_sensitive_data(&encoded_data)?;
let key_store: KeyStore =
serde_json::from_slice(&key_data).context("Failed to deserialize key store")?;
Ok(Some(key_store))
} else {
Ok(None)
}
}
async fn load_key_from_keychain(&self, line: &str) -> Result<Option<KeyStore>> {
let label = match line.split("bell_key_").nth(1) {
Some(l) => l,
None => return Ok(None),
};
let output = std::process::Command::new("security")
.args(&[
"find-generic-password",
"-s",
&format!("bell_key_{}", label),
"-w",
])
.output()
.context("Failed to read key from keychain")?;
if output.status.success() {
let encoded_data = String::from_utf8_lossy(&output.stdout);
let key_data = decode_sensitive_data(&encoded_data)?;
let key_store: KeyStore =
serde_json::from_slice(&key_data).context("Failed to deserialize key store")?;
Ok(Some(key_store))
} else {
Ok(None)
}
}
async fn load_key_from_bellande(&self, line: &str) -> Result<Option<KeyStore>> {
let label = match line.split("bell_key_").nth(1) {
Some(l) => l,
None => return Ok(None),
};
let output = std::process::Command::new("bellctl")
.args(&["secure-store", "get", &format!("bell_key_{}", label)])
.output()
.context("Failed to read key from BellandeOS secure store")?;
if output.status.success() {
let encoded_data = String::from_utf8_lossy(&output.stdout);
let key_data = decode_sensitive_data(&encoded_data)?;
let key_store: KeyStore =
serde_json::from_slice(&key_data).context("Failed to deserialize key store")?;
Ok(Some(key_store))
} else {
Ok(None)
}
}
async fn load_master_key_from_keychain(&self) -> Result<Key<Aes256Gcm>> {
let output = std::process::Command::new("security")
.args(&["find-generic-password", "-s", "bell_master_key", "-w"])
.output()
.context("Failed to read from keychain")?;
if output.status.success() {
let encoded = String::from_utf8_lossy(&output.stdout);
let key_data = decode_sensitive_data(&encoded)?;
if key_data.len() != KEY_SIZE {
return Err(anyhow::anyhow!("Invalid key length"));
}
let key = Key::<Aes256Gcm>::from_slice(&key_data);
Ok(key.clone())
} else {
self.generate_and_store_master_key().await
}
}
async fn load_master_key_from_bellande(&self) -> Result<Key<Aes256Gcm>> {
let output = std::process::Command::new("bellctl")
.args(&["secure-store", "get", "bell_master_key"])
.output()
.context("Failed to read from BellandeOS secure store")?;
if output.status.success() {
let encoded = String::from_utf8_lossy(&output.stdout);
let key_data = decode_sensitive_data(&encoded)?;
if key_data.len() != KEY_SIZE {
return Err(anyhow::anyhow!("Invalid key length"));
}
let key = Key::<Aes256Gcm>::from_slice(&key_data);
Ok(key.clone())
} else {
self.generate_and_store_master_key().await
}
}
async fn save_keys(&self) -> Result<()> {
let store = self.key_store.read().await;
for key_store in store.values() {
let key_data = serde_json::to_string(key_store)?;
let encoded_data = encode_sensitive_data(key_data.as_bytes());
match std::env::consts::OS {
"macos" => {
std::process::Command::new("security")
.args(&[
"add-generic-password",
"-s",
&format!("bell_key_{}", key_store.label),
"-w",
&encoded_data,
])
.output()
.context("Failed to store in keychain")?;
}
"linux" => {
std::process::Command::new("keyctl")
.args(&[
"add",
"user",
&format!("bell_key_{}", key_store.label),
&encoded_data,
"@u",
])
.output()
.context("Failed to store in keyring")?;
}
"bellandeos" => {
std::process::Command::new("bellctl")
.args(&[
"secure-store",
"set",
&format!("bell_key_{}", key_store.label),
&encoded_data,
])
.output()
.context("Failed to store in BellandeOS secure store")?;
}
_ => {
let key_file = self.storage_path.join(format!("{}.key", key_store.label));
fs::write(key_file, &encoded_data)?;
}
}
}
Ok(())
}
pub async fn generate_key(
&self,
label: &str,
key_type: KeyType,
usage: KeyUsage,
) -> Result<Vec<u8>> {
let mut key = vec![0u8; KEY_SIZE];
OsRng.fill_bytes(&mut key);
let key_id = rand::random::<[u8; 8]>();
let master_key = self.load_master_key().await?;
let encrypted_key = self.encrypt_with_master_key(&master_key, &key).await?;
let fingerprint = calculate_key_fingerprint(&key);
let key_store = KeyStore {
label: label.to_string(),
key_id,
encrypted_key,
created_at: SystemTime::now(),
rotated_at: None,
fingerprint,
metadata: KeyMetadata {
algorithm: "AES-256-GCM".to_string(),
key_type,
usage,
platform: std::env::consts::OS.to_string(),
},
};
// Store in platform-specific secure storage
self.store_key_in_platform_storage(&key_store).await?;
// Update in-memory store
let mut store = self.key_store.write().await;
store.insert(label.to_string(), key_store);
// Save to disk
self.save_keys().await?;
Ok(key_id.to_vec())
}
pub async fn encrypt_data(&self, data: &str) -> Result<Vec<u8>> {
let master_key = self.load_master_key().await?;
let cipher = Aes256Gcm::new(&master_key);
let mut nonce = [0u8; NONCE_SIZE];
OsRng.fill_bytes(&mut nonce);
let nonce = Nonce::from_slice(&nonce);
let ciphertext = cipher
.encrypt(nonce, data.as_bytes())
.context("Failed to encrypt data")?;
let mut result = Vec::with_capacity(NONCE_SIZE + ciphertext.len());
result.extend_from_slice(nonce);
result.extend_from_slice(&ciphertext);
log_crypto_operation("ENCRYPT", &result).await?;
Ok(result)
}
pub async fn decrypt_data(&self, data: &[u8]) -> Result<String> {
if data.len() < NONCE_SIZE {
anyhow::bail!("Invalid encrypted data");
}
let master_key = self.load_master_key().await?;
let cipher = Aes256Gcm::new(&master_key);
let nonce = Nonce::from_slice(&data[..NONCE_SIZE]);
let ciphertext = &data[NONCE_SIZE..];
let plaintext = cipher
.decrypt(nonce, ciphertext)
.context("Failed to decrypt data")?;
log_crypto_operation("DECRYPT", data).await?;
String::from_utf8(plaintext).context("Failed to convert decrypted data to string")
}
async fn load_master_key_from_keyring(&self) -> Result<Key<Aes256Gcm>> {
let output = std::process::Command::new("keyctl")
.args(&["read", "user", "bell_master_key"])
.output()
.context("Failed to read from keyring")?;
if output.status.success() {
let encoded = String::from_utf8_lossy(&output.stdout);
let key_data = decode_sensitive_data(&encoded)?;
if key_data.len() != KEY_SIZE {
return Err(anyhow::anyhow!("Invalid key length"));
}
let key = Key::<Aes256Gcm>::from_slice(&key_data);
Ok(key.clone())
} else {
self.generate_and_store_master_key().await
}
}
async fn load_master_key(&self) -> Result<Key<Aes256Gcm>> {
match std::env::consts::OS {
"macos" => self.load_master_key_from_keychain().await,
"linux" => self.load_master_key_from_keyring().await,
"bellandeos" => self.load_master_key_from_bellande().await,
_ => self.load_master_key_from_file().await,
}
}
async fn load_master_key_from_file(&self) -> Result<Key<Aes256Gcm>> {
let master_key_path = self.storage_path.join("master.key");
if master_key_path.exists() {
let mut file =
File::open(&master_key_path).context("Failed to open master key file")?;
let mut key_bytes = [0u8; KEY_SIZE];
file.read_exact(&mut key_bytes)
.context("Failed to read master key")?;
let key = Key::<Aes256Gcm>::from_slice(&key_bytes);
Ok(key.clone())
} else {
self.generate_and_store_master_key().await
}
}
async fn encrypt_with_master_key(
&self,
master_key: &Key<Aes256Gcm>,
data: &[u8],
) -> Result<Vec<u8>> {
let cipher = Aes256Gcm::new(master_key);
let nonce = Nonce::from_slice(&[0u8; NONCE_SIZE]);
cipher
.encrypt(nonce, data)
.context("Failed to encrypt with master key")
}
async fn check_key_rotation(&self) -> Result<()> {
let mut store = self.key_store.write().await;
let now = SystemTime::now();
for key_store in store.values_mut() {
let last_rotation = key_store.rotated_at.unwrap_or(key_store.created_at);
if now.duration_since(last_rotation)?.as_secs() > KEY_ROTATION_DAYS * 24 * 60 * 60 {
let mut new_key = vec![0u8; KEY_SIZE];
OsRng.fill_bytes(&mut new_key);
let master_key = self.load_master_key().await?;
key_store.encrypted_key =
self.encrypt_with_master_key(&master_key, &new_key).await?;
key_store.rotated_at = Some(now);
key_store.fingerprint = calculate_key_fingerprint(&new_key);
self.store_key_in_platform_storage(key_store).await?;
}
}
Ok(())
}
// Platform-specific implementations
async fn initialize_keychain(&self) -> Result<()> {
let output = std::process::Command::new("security")
.args(&["create-keychain", "bell.keychain"])
.output()
.context("Failed to create keychain")?;
if !output.status.success() {
warn!("Keychain already exists or creation failed");
}
Ok(())
}
async fn initialize_keyring(&self) -> Result<()> {
let output = std::process::Command::new("keyctl")
.args(&["new_session"])
.output()
.context("Failed to create keyring session")?;
if !output.status.success() {
warn!("Keyring session creation failed");
}
Ok(())
}
async fn initialize_bellande_secure_store(&self) -> Result<()> {
let output = std::process::Command::new("bellctl")
.args(&["secure-store", "init"])
.output()
.context("Failed to initialize BellandeOS secure store")?;
if !output.status.success() {
warn!("BellandeOS secure store initialization failed");
}
Ok(())
}
async fn generate_and_store_master_key(&self) -> Result<Key<Aes256Gcm>> {
let mut key_bytes = [0u8; KEY_SIZE];
OsRng.fill_bytes(&mut key_bytes);
let key = Key::<Aes256Gcm>::from_slice(&key_bytes).clone();
let encoded_key = encode_sensitive_data(&key_bytes);
match std::env::consts::OS {
"macos" => {
std::process::Command::new("security")
.args(&[
"add-generic-password",
"-s",
"bell_master_key",
"-w",
&encoded_key,
])
.output()
.context("Failed to store in keychain")?;
}
"linux" => {
std::process::Command::new("keyctl")
.args(&["add", "user", "bell_master_key", &encoded_key, "@u"])
.output()
.context("Failed to store in keyring")?;
}
"bellandeos" => {
std::process::Command::new("bellctl")
.args(&["secure-store", "set", "bell_master_key", &encoded_key])
.output()
.context("Failed to store in BellandeOS secure store")?;
}
_ => {
self.store_master_key_to_file(&key_bytes).await?;
}
}
Ok(key)
}
async fn store_master_key_to_file(&self, key: &[u8]) -> Result<()> {
let master_key_path = self.storage_path.join("master.key");
let mut options = OpenOptions::new();
options.write(true).create(true).truncate(true);
let mut file = set_secure_permissions(&mut options)
.open(&master_key_path)
.context("Failed to create master key file")?;
file.write_all(key).context("Failed to write master key")?;
file.sync_all().context("Failed to sync master key file")?;
Ok(())
}
async fn store_key_in_platform_storage(&self, key_store: &KeyStore) -> Result<()> {
let key_data = serde_json::to_string(key_store)?;
let encoded_data = encode_sensitive_data(key_data.as_bytes());
match std::env::consts::OS {
"macos" => self.store_key_in_keychain(key_store, &encoded_data).await?,
"linux" => self.store_key_in_keyring(key_store, &encoded_data).await?,
"bellandeos" => self.store_key_in_bellande(key_store, &encoded_data).await?,
_ => self.store_key_in_file(key_store, &encoded_data).await?,
}
Ok(())
}
async fn store_key_in_keychain(&self, key_store: &KeyStore, encoded_data: &str) -> Result<()> {
let output = std::process::Command::new("security")
.args(&[
"add-generic-password",
"-s",
&format!("bell_key_{}", key_store.label),
"-w",
encoded_data,
])
.output()
.context("Failed to store key in keychain")?;
if !output.status.success() {
// Try to delete existing entry first and retry
let _ = std::process::Command::new("security")
.args(&[
"delete-generic-password",
"-s",
&format!("bell_key_{}", key_store.label),
])
.output();
std::process::Command::new("security")
.args(&[
"add-generic-password",
"-s",
&format!("bell_key_{}", key_store.label),
"-w",
encoded_data,
])
.output()
.context("Failed to store key in keychain after deletion")?;
}
Ok(())
}
async fn store_key_in_keyring(&self, key_store: &KeyStore, encoded_data: &str) -> Result<()> {
// First, try to remove any existing key
let _ = std::process::Command::new("keyctl")
.args(&["unlink", &format!("bell_key_{}", key_store.label), "@u"])
.output();
let output = std::process::Command::new("keyctl")
.args(&[
"add",
"user",
&format!("bell_key_{}", key_store.label),
encoded_data,
"@u",
])
.output()
.context("Failed to store key in keyring")?;
if !output.status.success() {
anyhow::bail!("Failed to store key in keyring: {:?}", output);
}
Ok(())
}
async fn store_key_in_bellande(&self, key_store: &KeyStore, encoded_data: &str) -> Result<()> {
let output = std::process::Command::new("bellctl")
.args(&[
"secure-store",
"set",
&format!("bell_key_{}", key_store.label),
encoded_data,
])
.output()
.context("Failed to store key in BellandeOS secure store")?;
if !output.status.success() {
// Try to delete and retry
let _ = std::process::Command::new("bellctl")
.args(&[
"secure-store",
"delete",
&format!("bell_key_{}", key_store.label),
])
.output();
std::process::Command::new("bellctl")
.args(&[
"secure-store",
"set",
&format!("bell_key_{}", key_store.label),
encoded_data,
])
.output()
.context("Failed to store key in BellandeOS secure store after deletion")?;
}
Ok(())
}
async fn store_key_in_file(&self, key_store: &KeyStore, encoded_data: &str) -> Result<()> {
let key_file = self.storage_path.join(format!("{}.key", key_store.label));
// Create a temporary file first
let temp_file = key_file.with_extension("tmp");
// Write to temporary file
let mut options = OpenOptions::new();
options.write(true).create(true).truncate(true);
let mut file = set_secure_permissions(&mut options)
.open(&temp_file)
.context("Failed to create temporary key file")?;
file.write_all(encoded_data.as_bytes())
.context("Failed to write key data")?;
file.sync_all().context("Failed to sync key file")?;
// Atomically rename temporary file to final location
fs::rename(&temp_file, &key_file).context("Failed to save key file")?;
Ok(())
}
async fn load_keys(&self) -> Result<()> {
let mut store = self.key_store.write().await;
match std::env::consts::OS {
"macos" => {
let output = std::process::Command::new("security")
.args(&["dump-keychain"])
.output()
.context("Failed to dump keychain")?;
if output.status.success() {
for line in String::from_utf8_lossy(&output.stdout).lines() {
if line.contains("bell_key_") {
if let Some(key_store) = self.load_key_from_keychain(line).await? {
store.insert(key_store.label.clone(), key_store);
}
}
}
}
}
"linux" => {
let output = std::process::Command::new("keyctl")
.args(&["list", "@u"])
.output()
.context("Failed to list keyring")?;
if output.status.success() {
for line in String::from_utf8_lossy(&output.stdout).lines() {
if line.contains("bell_key_") {
if let Some(key_store) = self.load_key_from_keyring(line).await? {
store.insert(key_store.label.clone(), key_store);
}
}
}
}
}
"bellandeos" => {
let output = std::process::Command::new("bellctl")
.args(&["secure-store", "list"])
.output()
.context("Failed to list BellandeOS secure store")?;
if output.status.success() {
for line in String::from_utf8_lossy(&output.stdout).lines() {
if line.contains("bell_key_") {
if let Some(key_store) = self.load_key_from_bellande(line).await? {
store.insert(key_store.label.clone(), key_store);
}
}
}
}
}
_ => {
// Fallback to file-based storage
if let Ok(entries) = fs::read_dir(&self.storage_path) {
for entry in entries {
if let Ok(entry) = entry {
if let Some(filename) = entry.file_name().to_str() {
if filename.ends_with(".key") {
if let Ok(key_data) = fs::read_to_string(entry.path()) {
match serde_json::from_str::<KeyStore>(&key_data) {
Ok(key_store) => {
store.insert(key_store.label.clone(), key_store);
}
Err(err) => {
warn!("Failed to deserialize key store: {}", err);
continue;
}
}
}
}
}
}
}
}
}
}
Ok(())
}
}
fn get_platform_storage_path() -> Result<PathBuf> {
let path = match std::env::consts::OS {
"macos" => PathBuf::from("/Library/Application Support/bell/secure"),
"linux" => PathBuf::from("/var/lib/bell/secure"),
"bellandeos" => PathBuf::from("/bell/secure/storage"),
_ => {
let mut path = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
path.push("secure");
path
}
};
Ok(path)
}
fn ensure_secure_directory(path: &Path) -> Result<()> {
if !path.exists() {
fs::create_dir_all(path)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(path, fs::Permissions::from_mode(0o700))?;
}
}
Ok(())
}
fn calculate_key_fingerprint(key: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(key);
format!("{:x}", hasher.finalize())
}
async fn log_crypto_operation(operation: &str, data: &[u8]) -> Result<()> {
let fingerprint = calculate_key_fingerprint(data);
info!(
"Crypto operation: {} - Size: {} bytes - Fingerprint: {}",
operation,
data.len(),
fingerprint
);
Ok(())
}
pub async fn encrypt_data(data: &str) -> anyhow::Result<String> {
let storage = SecureStorage::new().await?;
let encrypted = storage.encrypt_data(data).await?;
Ok(base64::engine::general_purpose::STANDARD.encode(&encrypted))
}
pub async fn decrypt_data(encrypted_data: &str) -> anyhow::Result<String> {
let storage = SecureStorage::new().await?;
let data = base64::engine::general_purpose::STANDARD.decode(encrypted_data)?;
storage.decrypt_data(&data).await
}

1
src/hsm/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod hsm;

1
src/network/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod network;

522
src/network/network.rs Normal file
View File

@@ -0,0 +1,522 @@
// Copyright (C) 2024 Bellande Architecture Mechanism Research Innovation Center, Ronaldson Bellande
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::net::IpAddr;
use std::path::PathBuf;
use std::process::Command;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use ipnetwork::Ipv4Network;
use log::{error, info, warn};
use serde::{Deserialize, Serialize};
use tokio::time::sleep;
use crate::config::config::Config;
#[derive(Debug, Serialize, Deserialize)]
pub struct NetworkConfig {
pub interface: String,
pub namespace: String,
pub allowed_ports: Vec<u16>,
pub dns_servers: Vec<String>,
pub retry_attempts: u32,
pub retry_delay: u64,
}
impl Default for NetworkConfig {
fn default() -> Self {
NetworkConfig {
interface: get_default_interface(),
namespace: "bell_isolated".to_string(),
allowed_ports: vec![53, 80, 443], // DNS, HTTP, HTTPS
dns_servers: vec!["8.8.8.8".to_string(), "8.8.4.4".to_string()],
retry_attempts: 3,
retry_delay: 1,
}
}
}
#[derive(Debug)]
struct NetworkCommands {
down_cmd: Vec<String>,
up_cmd: Vec<String>,
flush_cmd: Vec<String>,
firewall_cmd: Vec<String>,
}
impl NetworkCommands {
fn new() -> Self {
match std::env::consts::OS {
"macos" => Self {
down_cmd: vec!["ifconfig".into(), "{interface}".into(), "down".into()],
up_cmd: vec!["ifconfig".into(), "{interface}".into(), "up".into()],
flush_cmd: vec![
"ifconfig".into(),
"{interface}".into(),
"inet".into(),
"0".into(),
],
firewall_cmd: vec!["pfctl".into(), "-f".into(), "/etc/pf.conf".into()],
},
"linux" => Self {
down_cmd: vec![
"ip".into(),
"link".into(),
"set".into(),
"{interface}".into(),
"down".into(),
],
up_cmd: vec![
"ip".into(),
"link".into(),
"set".into(),
"{interface}".into(),
"up".into(),
],
flush_cmd: vec![
"ip".into(),
"addr".into(),
"flush".into(),
"dev".into(),
"{interface}".into(),
],
firewall_cmd: vec!["iptables".into(), "-F".into()],
},
"bellandeos" => Self {
down_cmd: vec![
"bellctl".into(),
"net".into(),
"down".into(),
"{interface}".into(),
],
up_cmd: vec![
"bellctl".into(),
"net".into(),
"up".into(),
"{interface}".into(),
],
flush_cmd: vec![
"bellctl".into(),
"net".into(),
"flush".into(),
"{interface}".into(),
],
firewall_cmd: vec!["bellctl".into(), "firewall".into(), "reset".into()],
},
_ => Self {
down_cmd: vec![],
up_cmd: vec![],
flush_cmd: vec![],
firewall_cmd: vec![],
},
}
}
}
#[derive(Debug, Serialize, Deserialize)]
struct AuditEvent {
timestamp: DateTime<Utc>,
event_type: String,
user: String,
message: String,
source_ip: Option<String>,
severity: AuditSeverity,
}
#[derive(Debug, Serialize, Deserialize)]
enum AuditSeverity {
Info,
Warning,
Error,
Critical,
}
impl Default for AuditSeverity {
fn default() -> Self {
AuditSeverity::Info
}
}
pub async fn isolate_network() -> Result<()> {
let config = NetworkConfig::default();
let commands = NetworkCommands::new();
info!(
"Starting network isolation process for {}",
std::env::consts::OS
);
let down_cmd = replace_interface_placeholder(&commands.down_cmd, &config.interface);
if let Some((cmd, args)) = down_cmd.split_first() {
run_command(cmd, args).await?;
}
let flush_cmd = replace_interface_placeholder(&commands.flush_cmd, &config.interface);
if let Some((cmd, args)) = flush_cmd.split_first() {
run_command(cmd, args).await?;
}
setup_firewall_rules(&config).await?;
log_audit_event(
"NETWORK_ISOLATION",
"SYSTEM",
&format!(
"Network isolated on {}: {}",
std::env::consts::OS,
config.interface
),
)
.await?;
Ok(())
}
pub async fn restore_network() -> Result<()> {
let config = NetworkConfig::default();
let commands = NetworkCommands::new();
info!(
"Starting network restoration process for {}",
std::env::consts::OS
);
let up_cmd = replace_interface_placeholder(&commands.up_cmd, &config.interface);
if let Some((cmd, args)) = up_cmd.split_first() {
run_command(cmd, args).await?;
}
let mut attempts = 0;
while attempts < config.retry_attempts {
match request_dhcp_lease(&config.interface).await {
Ok(_) => break,
Err(e) => {
warn!("DHCP request failed, attempt {}: {}", attempts + 1, e);
if attempts + 1 == config.retry_attempts {
return Err(e);
}
sleep(Duration::from_secs(config.retry_delay)).await;
attempts += 1;
}
}
}
log_audit_event(
"NETWORK_RESTORATION",
"SYSTEM",
&format!(
"Network restored on {}: {}",
std::env::consts::OS,
config.interface
),
)
.await?;
Ok(())
}
async fn run_command(cmd: &str, args: &[String]) -> Result<()> {
let status = Command::new(cmd)
.args(args)
.status()
.context(format!("Failed to run command: {} {:?}", cmd, args))?;
if !status.success() {
error!("Command failed: {} {:?}", cmd, args);
anyhow::bail!("Command failed with status: {}", status);
}
Ok(())
}
fn replace_interface_placeholder(cmd: &[String], interface: &str) -> Vec<String> {
cmd.iter()
.map(|s| s.replace("{interface}", interface))
.collect()
}
async fn setup_firewall_rules(config: &NetworkConfig) -> Result<()> {
match std::env::consts::OS {
"macos" => setup_pf_firewall(config).await?,
"linux" => setup_iptables_firewall(config).await?,
"bellandeos" => setup_bell_firewall(config).await?,
_ => anyhow::bail!("Unsupported operating system"),
}
Ok(())
}
async fn setup_pf_firewall(config: &NetworkConfig) -> Result<()> {
let pf_rules = generate_pf_rules(config);
std::fs::write("/etc/pf.conf", pf_rules).context("Failed to write PF configuration")?;
run_command("pfctl", &["-f".to_string(), "/etc/pf.conf".to_string()])
.await
.context("Failed to load PF rules")?;
run_command("pfctl", &["-e".to_string()])
.await
.context("Failed to enable PF firewall")?;
Ok(())
}
async fn setup_iptables_firewall(config: &NetworkConfig) -> Result<()> {
run_command("iptables", &["-F".to_string()]).await?;
for port in &config.allowed_ports {
let port_str = port.to_string();
let args = vec![
"-A".to_string(),
"OUTPUT".to_string(),
"-p".to_string(),
"tcp".to_string(),
"--dport".to_string(),
port_str,
"-j".to_string(),
"ACCEPT".to_string(),
];
run_command("iptables", &args).await?;
}
run_command(
"iptables",
&["-P".to_string(), "OUTPUT".to_string(), "DROP".to_string()],
)
.await
}
async fn setup_bell_firewall(config: &NetworkConfig) -> Result<()> {
run_command("bellctl", &["firewall".to_string(), "reset".to_string()]).await?;
for port in &config.allowed_ports {
let port_str = port.to_string();
let args = vec![
"firewall".to_string(),
"allow".to_string(),
"port".to_string(),
port_str,
];
run_command("bellctl", &args).await?;
}
run_command(
"bellctl",
&["firewall".to_string(), "default-deny".to_string()],
)
.await
}
fn generate_pf_rules(config: &NetworkConfig) -> String {
let mut rules = String::new();
rules.push_str("# Generated PF rules\n");
rules.push_str("set skip on lo0\n");
rules.push_str("set block-policy drop\n");
rules.push_str("\n# Default deny all\n");
rules.push_str("block all\n\n");
// Allow DNS to specified servers
rules.push_str("# Allow DNS to specified servers\n");
for dns in &config.dns_servers {
rules.push_str(&format!("pass out proto udp to {} port 53\n", dns));
}
// Allow specified ports
rules.push_str("\n# Allow specified outbound ports\n");
for port in &config.allowed_ports {
rules.push_str(&format!("pass out proto tcp to any port {}\n", port));
}
// Security rules
rules.push_str("\n# Security rules\n");
rules.push_str("block in quick from urpf-failed\n");
rules.push_str("block in quick from { 10/8, 172.16/12, 192.168/16 } to any\n");
rules.push_str("block in quick from any to { 10/8, 172.16/12, 192.168/16 }\n");
rules
}
fn get_default_interface() -> String {
match std::env::consts::OS {
"macos" => "en0".to_string(),
"linux" => "eth0".to_string(),
"bellandeos" => "bell0".to_string(),
_ => "unknown".to_string(),
}
}
async fn request_dhcp_lease(interface: &str) -> Result<()> {
match std::env::consts::OS {
"macos" => {
run_command(
"ipconfig",
&["set".to_string(), interface.to_string(), "DHCP".to_string()],
)
.await?;
}
"linux" => {
run_command("dhclient", &[interface.to_string()]).await?;
}
"bellandeos" => {
run_command(
"bellctl",
&["net".to_string(), "dhcp".to_string(), interface.to_string()],
)
.await?;
}
_ => anyhow::bail!("Unsupported operating system"),
}
Ok(())
}
async fn log_audit_event(event_type: &str, user: &str, message: &str) -> Result<()> {
let event = AuditEvent {
timestamp: Utc::now(),
event_type: event_type.to_string(),
user: user.to_string(),
message: message.to_string(),
source_ip: get_source_ip().await,
severity: get_event_severity(event_type),
};
info!(
"Audit: {} - {}: {}",
event.event_type, event.user, event.message
);
write_audit_log(&event).await?;
if matches!(event.severity, AuditSeverity::Critical) {
flush_audit_log().await?;
}
Ok(())
}
async fn get_source_ip() -> Option<String> {
match local_ip_address::local_ip() {
Ok(ip) => Some(ip.to_string()),
Err(_) => None,
}
}
fn get_event_severity(event_type: &str) -> AuditSeverity {
match event_type {
"NETWORK_ISOLATION" | "NETWORK_RESTORATION" => AuditSeverity::Warning,
"NETWORK_CHECK" => AuditSeverity::Info,
"SECURITY_VIOLATION" | "NETWORK_ATTACK" => AuditSeverity::Critical,
_ => AuditSeverity::Info,
}
}
async fn write_audit_log(event: &AuditEvent) -> Result<()> {
let log_path = get_audit_log_path();
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(&log_path)
.context(format!("Failed to open audit log file: {:?}", log_path))?;
let log_entry = serde_json::to_string(&event).context("Failed to serialize audit event")?;
writeln!(file, "{}", log_entry).context("Failed to write to audit log")?;
Ok(())
}
async fn flush_audit_log() -> Result<()> {
let log_path = get_audit_log_path();
let mut file = OpenOptions::new()
.append(true)
.open(&log_path)
.context("Failed to open audit log for flushing")?;
file.sync_all()
.context("Failed to flush audit log to disk")?;
Ok(())
}
fn get_audit_log_path() -> PathBuf {
match std::env::consts::OS {
"macos" => PathBuf::from("/var/log/security/audit.log"),
"linux" => PathBuf::from("/var/log/audit/audit.log"),
"bellandeos" => PathBuf::from("/bell/logs/audit/system.log"),
_ => PathBuf::from("audit.log"),
}
}
pub async fn rotate_audit_logs() -> Result<()> {
let log_path = get_audit_log_path();
if let Ok(metadata) = std::fs::metadata(&log_path) {
// Rotate if file is larger than 10MB
if metadata.len() > 10_000_000 {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let backup_path = log_path.with_extension(format!("log.{}", timestamp));
std::fs::rename(&log_path, &backup_path).context("Failed to rotate audit log")?;
File::create(&log_path).context("Failed to create new audit log after rotation")?;
log_audit_event(
"AUDIT_LOG_ROTATION",
"SYSTEM",
&format!("Rotated audit log to {:?}", backup_path),
)
.await?;
}
}
Ok(())
}
pub async fn is_network_allowed(config: &Config) -> Result<bool> {
let local_ip = local_ip_address::local_ip().context("Failed to get local IP address")?;
for network_str in &config.allowed_networks {
let network: Ipv4Network = network_str
.parse()
.context("Failed to parse network configuration")?;
if let IpAddr::V4(ipv4) = local_ip {
if network.contains(ipv4) {
log_audit_event(
"NETWORK_CHECK",
"SYSTEM",
&format!("Network allowed: {}", local_ip),
)
.await?;
return Ok(true);
}
}
}
log_audit_event(
"NETWORK_CHECK",
"SYSTEM",
&format!("Network denied: {}", local_ip),
)
.await?;
Ok(false)
}

View File

@@ -0,0 +1,2 @@
pub mod privilege;
pub mod user;

View File

@@ -0,0 +1,314 @@
// Copyright (C) 2024 Bellande Architecture Mechanism Research Innovation Center, Ronaldson Bellande
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::collections::HashMap;
use std::fmt;
use std::hash::Hash;
use std::str::FromStr;
use std::time::{Duration, SystemTime};
use anyhow::Result;
use log::error;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::audit::audit::log_audit_event;
use crate::config::config::Config;
use crate::user_privilege::user::User;
use chrono::Timelike;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Clone, Copy)]
pub enum PrivilegeLevel {
User, // Basic user privileges
Group, // Group-based privileges
Administrator, // Administrative privileges
Root, // Root-level access
Bell, // Highest level - system owner
}
#[derive(Error, Debug)]
pub enum PrivilegeLevelError {
#[error("Invalid privilege level: {0}")]
InvalidPrivilegeLevel(String),
#[error("Insufficient privileges")]
InsufficientPrivileges,
#[error("Expired privileges")]
ExpiredPrivileges,
#[error("Group not found: {0}")]
GroupNotFound(String),
#[error("Permission not found: {0}")]
PermissionNotFound(String),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PrivilegeConfig {
pub elevation_timeout: Duration,
pub require_mfa: bool,
pub allowed_elevation_hours: Vec<u8>,
pub max_concurrent_elevations: usize,
pub restricted_commands: HashMap<PrivilegeLevel, Vec<String>>,
}
impl Default for PrivilegeConfig {
fn default() -> Self {
Self {
elevation_timeout: Duration::from_secs(3600),
require_mfa: true,
allowed_elevation_hours: (0..24).collect(),
max_concurrent_elevations: 3,
restricted_commands: HashMap::new(),
}
}
}
#[derive(Debug)]
pub struct PrivilegeManager {
config: PrivilegeConfig,
active_elevations: HashMap<String, Vec<PrivilegeElevation>>,
}
#[derive(Debug)]
struct PrivilegeElevation {
level: PrivilegeLevel,
granted_at: SystemTime,
expires_at: SystemTime,
reason: String,
}
impl FromStr for PrivilegeLevel {
type Err = PrivilegeLevelError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"user" => Ok(PrivilegeLevel::User),
"group" => Ok(PrivilegeLevel::Group),
"admin" | "administrator" => Ok(PrivilegeLevel::Administrator),
"root" => Ok(PrivilegeLevel::Root),
"bell" => Ok(PrivilegeLevel::Bell),
_ => Err(PrivilegeLevelError::InvalidPrivilegeLevel(s.to_string())),
}
}
}
impl fmt::Display for PrivilegeLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PrivilegeLevel::User => write!(f, "user"),
PrivilegeLevel::Group => write!(f, "group"),
PrivilegeLevel::Administrator => write!(f, "administrator"),
PrivilegeLevel::Root => write!(f, "root"),
PrivilegeLevel::Bell => write!(f, "bell"),
}
}
}
impl PrivilegeManager {
pub fn new(config: PrivilegeConfig) -> Self {
Self {
config,
active_elevations: HashMap::new(),
}
}
pub async fn check_permission(
&self,
user: &User,
required_privilege: PrivilegeLevel,
config: &Config,
) -> Result<bool> {
// Direct privilege level check
if user.privilege >= required_privilege {
log_audit_event(
"PRIVILEGE_CHECK",
&user.username,
&format!("Direct privilege granted: {:?}", required_privilege),
)
.await?;
return Ok(true);
}
// Check active elevations
if let Some(elevations) = self.active_elevations.get(&user.username) {
for elevation in elevations {
if elevation.level >= required_privilege && SystemTime::now() < elevation.expires_at
{
log_audit_event(
"PRIVILEGE_CHECK",
&user.username,
&format!("Elevation privilege granted: {:?}", required_privilege),
)
.await?;
return Ok(true);
}
}
}
// Check group permissions
for group_name in &user.groups {
if let Some(group) = config.groups.iter().find(|g| g.name == *group_name) {
if group.permissions.contains(&required_privilege.to_string()) {
log_audit_event(
"PRIVILEGE_CHECK",
&user.username,
&format!(
"Group privilege granted: {:?} from {}",
required_privilege, group_name
),
)
.await?;
return Ok(true);
}
}
}
log_audit_event(
"PRIVILEGE_CHECK",
&user.username,
&format!("Permission denied for: {:?}", required_privilege),
)
.await?;
Ok(false)
}
pub async fn elevate_privilege(
&mut self,
user: &User,
requested_level: PrivilegeLevel,
reason: &str,
mfa_token: Option<&str>,
) -> Result<()> {
// Check if elevation is allowed at current hour
let current_hour = chrono::Local::now().hour() as u8;
if !self.config.allowed_elevation_hours.contains(&current_hour) {
return Err(PrivilegeLevelError::InsufficientPrivileges.into());
}
// Check MFA requirement
if self.config.require_mfa && mfa_token.is_none() {
return Err(anyhow::anyhow!(
"MFA token required for privilege elevation"
));
}
// Check concurrent elevations
let user_elevations = self
.active_elevations
.entry(user.username.clone())
.or_default();
if user_elevations.len() >= self.config.max_concurrent_elevations {
return Err(anyhow::anyhow!("Maximum concurrent elevations reached"));
}
// Create new elevation
let elevation = PrivilegeElevation {
level: requested_level,
granted_at: SystemTime::now(),
expires_at: SystemTime::now() + self.config.elevation_timeout,
reason: reason.to_string(),
};
user_elevations.push(elevation);
log_audit_event(
"PRIVILEGE_ELEVATION",
&user.username,
&format!("Elevated to {:?} for reason: {}", requested_level, reason),
)
.await?;
Ok(())
}
pub async fn revoke_elevation(&mut self, user: &str, level: PrivilegeLevel) -> Result<()> {
if let Some(elevations) = self.active_elevations.get_mut(user) {
elevations.retain(|e| e.level != level);
log_audit_event(
"PRIVILEGE_REVOCATION",
user,
&format!("Revoked elevation: {:?}", level),
)
.await?;
}
Ok(())
}
pub fn cleanup_expired_elevations(&mut self) {
let now = SystemTime::now();
for elevations in self.active_elevations.values_mut() {
elevations.retain(|e| e.expires_at > now);
}
}
}
// OS-specific privilege checks
pub async fn check_os_specific_privileges(
user: &User,
required_privilege: PrivilegeLevel,
) -> Result<bool> {
match std::env::consts::OS {
"macos" => check_macos_privileges(user, required_privilege).await,
"linux" => check_linux_privileges(user, required_privilege).await,
"bellandeos" => check_bellande_privileges(user, required_privilege).await,
_ => Ok(false),
}
}
async fn check_macos_privileges(user: &User, required_privilege: PrivilegeLevel) -> Result<bool> {
// Check admin group membership
if required_privilege >= PrivilegeLevel::Administrator {
let output = std::process::Command::new("dseditgroup")
.args(&["-o", "checkmember", "-m", &user.username, "admin"])
.output()?;
if !output.status.success() {
return Ok(false);
}
}
Ok(true)
}
async fn check_linux_privileges(user: &User, required_privilege: PrivilegeLevel) -> Result<bool> {
// Check sudo group membership
if required_privilege >= PrivilegeLevel::Administrator {
let output = std::process::Command::new("groups")
.arg(&user.username)
.output()?;
let groups = String::from_utf8_lossy(&output.stdout);
if !groups.contains("sudo") && !groups.contains("wheel") {
return Ok(false);
}
}
Ok(true)
}
async fn check_bellande_privileges(
user: &User,
required_privilege: PrivilegeLevel,
) -> Result<bool> {
// Check BellandeOS specific privileges
let output = std::process::Command::new("bellctl")
.args(&[
"user",
"check-privilege",
&user.username,
&required_privilege.to_string(),
])
.output()?;
Ok(output.status.success())
}

675
src/user_privilege/user.rs Normal file
View File

@@ -0,0 +1,675 @@
// Copyright (C) 2024 Bellande Architecture Mechanism Research Innovation Center, Ronaldson Bellande
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::time::Duration;
use anyhow::{Context, Result};
use argon2::{
password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
Argon2,
};
use chrono::{DateTime, Utc};
use log::error;
use serde::{Deserialize, Serialize};
use std::io::Write;
use thiserror::Error;
use totp_rs::Secret;
use crate::audit::audit::log_audit_event;
use crate::config::config::Config;
use crate::user_privilege::privilege::PrivilegeLevel;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct User {
pub username: String,
pub password_hash: String,
pub privilege: PrivilegeLevel,
pub totp_secret: String,
pub groups: Vec<String>,
pub created_at: DateTime<Utc>,
pub last_login: Option<DateTime<Utc>>,
pub password_changed_at: DateTime<Utc>,
pub failed_login_attempts: u32,
pub locked_until: Option<DateTime<Utc>>,
pub settings: UserSettings,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct UserSettings {
pub require_mfa: bool,
pub password_expiry_days: u32,
pub max_failed_attempts: u32,
pub lockout_duration: Duration,
pub allowed_ip_ranges: Vec<String>,
}
#[derive(Error, Debug)]
pub enum UserError {
#[error("User not found: {0}")]
UserNotFound(String),
#[error("User already exists: {0}")]
UserExists(String),
#[error("Invalid password: {0}")]
InvalidPassword(String),
#[error("Account locked: {0}")]
AccountLocked(String),
#[error("Password expired")]
PasswordExpired,
#[error("Invalid group: {0}")]
InvalidGroup(String),
}
impl Default for UserSettings {
fn default() -> Self {
Self {
require_mfa: true,
password_expiry_days: 90,
max_failed_attempts: 5,
lockout_duration: Duration::from_secs(1800), // 30 minutes
allowed_ip_ranges: vec!["127.0.0.1/8".to_string()],
}
}
}
impl User {
pub fn new(username: &str, password: &str, privilege: PrivilegeLevel) -> Result<Self> {
let password_hash = hash_password(password)?;
let totp_secret = generate_totp_secret();
let now = Utc::now();
Ok(Self {
username: username.to_string(),
password_hash,
privilege,
totp_secret,
groups: Vec::new(),
created_at: now,
last_login: None,
password_changed_at: now,
failed_login_attempts: 0,
locked_until: None,
settings: UserSettings::default(),
})
}
pub fn is_locked(&self) -> bool {
if let Some(locked_until) = self.locked_until {
Utc::now() < locked_until
} else {
false
}
}
pub fn password_expired(&self) -> bool {
let expiry = chrono::Duration::days(self.settings.password_expiry_days as i64);
Utc::now() - self.password_changed_at > expiry
}
pub fn record_login_attempt(&mut self, success: bool) {
if success {
self.last_login = Some(Utc::now());
self.failed_login_attempts = 0;
self.locked_until = None;
} else {
self.failed_login_attempts += 1;
if self.failed_login_attempts >= self.settings.max_failed_attempts {
self.locked_until = Some(
Utc::now()
+ chrono::Duration::from_std(self.settings.lockout_duration).unwrap(),
);
}
}
}
}
pub async fn add_user(
config: &mut Config,
username: &str,
password: &str,
privilege: PrivilegeLevel,
) -> Result<()> {
// Check if user already exists
if config.users.iter().any(|u| u.username == username) {
return Err(UserError::UserExists(username.to_string()).into());
}
// Create new user
let new_user = User::new(username, password, privilege)?;
// Create OS-specific user account
create_os_user(username, privilege).await?;
config.users.push(new_user.clone());
config.save()?;
log_audit_event(
"USER_ADDED",
"SYSTEM",
&format!("Added user: {} with privilege: {:?}", username, privilege),
)
.await?;
println!(
"User added successfully. TOTP secret: {}",
new_user.totp_secret
);
Ok(())
}
pub async fn remove_user(config: &mut Config, username: &str) -> Result<()> {
// Check if user exists
if !config.users.iter().any(|u| u.username == username) {
return Err(UserError::UserNotFound(username.to_string()).into());
}
// Remove OS-specific user account
remove_os_user(username).await?;
config.users.retain(|u| u.username != username);
config.save()?;
log_audit_event(
"USER_REMOVED",
"SYSTEM",
&format!("Removed user: {}", username),
)
.await?;
println!("User removed successfully.");
Ok(())
}
pub async fn change_password(
config: &mut Config,
username: &str,
new_password: &str,
) -> Result<()> {
// Validate password complexity first
validate_password_complexity(new_password)?;
// Find user index
let user_index = config
.users
.iter()
.position(|u| u.username == username)
.ok_or_else(|| UserError::UserNotFound(username.to_string()))?;
// Update password
let new_hash = hash_password(new_password)?;
// Update the user's password
{
let user = &mut config.users[user_index];
user.password_hash = new_hash;
user.password_changed_at = Utc::now();
}
// Update OS-specific password
update_os_password(username, new_password).await?;
config.save()?;
log_audit_event(
"PASSWORD_CHANGED",
username,
"Password changed successfully",
)
.await?;
println!("Password changed successfully.");
Ok(())
}
pub async fn change_privilege(
config: &mut Config,
username: &str,
new_privilege: PrivilegeLevel,
) -> Result<()> {
// Find user index first
let user_index = config
.users
.iter()
.position(|u| u.username == username)
.ok_or_else(|| UserError::UserNotFound(username.to_string()))?;
// Get the values we need before modifying the user
let old_privilege = config.users[user_index].privilege;
let username_clone = config.users[user_index].username.clone();
// Update the privilege
config.users[user_index].privilege = new_privilege;
// Update OS-specific privileges
update_os_privileges(username, new_privilege).await?;
// Save the configuration
config.save()?;
// Log the audit event
log_audit_event(
"PRIVILEGE_CHANGED",
&username_clone,
&format!(
"Privilege changed from {:?} to {:?}",
old_privilege, new_privilege
),
)
.await?;
println!("Privilege level changed successfully.");
Ok(())
}
pub async fn add_user_to_group(
config: &mut Config,
username: &str,
group_name: &str,
) -> Result<()> {
// Check if group exists first
if !config.groups.iter().any(|g| g.name == group_name) {
return Err(UserError::InvalidGroup(group_name.to_string()).into());
}
// Find user index
let user_index = config
.users
.iter()
.position(|u| u.username == username)
.ok_or_else(|| UserError::UserNotFound(username.to_string()))?;
// Check if user is already in group
let already_in_group = config.users[user_index]
.groups
.contains(&group_name.to_string());
if !already_in_group {
// Get username for audit log before modification
let username_clone = config.users[user_index].username.clone();
// Add user to group
config.users[user_index].groups.push(group_name.to_string());
// Update OS-specific group membership
add_os_user_to_group(username, group_name).await?;
// Save configuration
config.save()?;
// Log audit event
log_audit_event(
"USER_ADDED_TO_GROUP",
&username_clone,
&format!("Added to group: {}", group_name),
)
.await?;
println!("User added to group successfully.");
} else {
println!("User is already in this group.");
}
Ok(())
}
pub async fn remove_user_from_group(
config: &mut Config,
username: &str,
group_name: &str,
) -> Result<()> {
// Find user index
let user_index = config
.users
.iter()
.position(|u| u.username == username)
.ok_or_else(|| UserError::UserNotFound(username.to_string()))?;
// Get username for audit log before modification
let username_clone = config.users[user_index].username.clone();
// Remove the group
config.users[user_index].groups.retain(|g| g != group_name);
// Update OS-specific group membership
remove_os_user_from_group(username, group_name).await?;
// Save configuration
config.save()?;
// Log audit event
log_audit_event(
"USER_REMOVED_FROM_GROUP",
&username_clone,
&format!("Removed from group: {}", group_name),
)
.await?;
println!("User removed from group successfully.");
Ok(())
}
// Helper functions
fn hash_password(password: &str) -> Result<String> {
// Generate a random salt
let salt = SaltString::generate(&mut OsRng);
// Create default Argon2 instance
let argon2 = Argon2::default();
// Hash the password
Ok(argon2
.hash_password(password.as_bytes(), &salt)?
.to_string())
}
// And here's a corresponding verify function you'll need
fn verify_password(hash: &str, password: &str) -> Result<bool> {
use argon2::password_hash::PasswordHash;
use argon2::PasswordVerifier;
// Parse the hash string into a PasswordHash instance
let parsed_hash = PasswordHash::new(hash)?;
// Verify the password against the hash
Ok(Argon2::default()
.verify_password(password.as_bytes(), &parsed_hash)
.is_ok())
}
fn generate_totp_secret() -> String {
Secret::generate_secret().to_string()
}
fn validate_password_complexity(password: &str) -> Result<()> {
if password.len() < 12 {
return Err(UserError::InvalidPassword("Password too short".to_string()).into());
}
let has_uppercase = password.chars().any(|c| c.is_uppercase());
let has_lowercase = password.chars().any(|c| c.is_lowercase());
let has_digit = password.chars().any(|c| c.is_digit(10));
let has_special = password.chars().any(|c| !c.is_alphanumeric());
if !(has_uppercase && has_lowercase && has_digit && has_special) {
return Err(UserError::InvalidPassword(
"Password does not meet complexity requirements".to_string(),
)
.into());
}
Ok(())
}
// OS-specific functions
async fn create_os_user(username: &str, privilege: PrivilegeLevel) -> Result<()> {
match std::env::consts::OS {
"macos" => create_macos_user(username, privilege).await,
"linux" => create_linux_user(username, privilege).await,
"bellandeos" => create_bellande_user(username, privilege).await,
_ => Ok(()),
}
}
async fn remove_os_user(username: &str) -> Result<()> {
match std::env::consts::OS {
"macos" => remove_macos_user(username).await,
"linux" => remove_linux_user(username).await,
"bellandeos" => remove_bellande_user(username).await,
_ => Ok(()),
}
}
async fn update_os_password(username: &str, password: &str) -> Result<()> {
match std::env::consts::OS {
"macos" => update_macos_password(username, password).await,
"linux" => update_linux_password(username, password).await,
"bellandeos" => update_bellande_password(username, password).await,
_ => Ok(()),
}
}
async fn update_os_privileges(username: &str, privilege: PrivilegeLevel) -> Result<()> {
match std::env::consts::OS {
"macos" => update_macos_privileges(username, privilege).await,
"linux" => update_linux_privileges(username, privilege).await,
"bellandeos" => update_bellande_privileges(username, privilege).await,
_ => Ok(()),
}
}
// OS-specific implementations for macOS, Linux, and BellandeOS...
async fn create_macos_user(username: &str, privilege: PrivilegeLevel) -> Result<()> {
let mut cmd = std::process::Command::new("sysadminctl");
cmd.args(&["-addUser", username]);
match privilege {
PrivilegeLevel::Administrator => {
cmd.arg("-admin");
}
_ => {}
}
cmd.output().context("Failed to create macOS user")?;
Ok(())
}
async fn create_linux_user(username: &str, privilege: PrivilegeLevel) -> Result<()> {
let mut cmd = std::process::Command::new("useradd");
cmd.arg(username);
match privilege {
PrivilegeLevel::Administrator => {
cmd.args(&["-G", "sudo"]);
}
_ => {}
}
cmd.output().context("Failed to create Linux user")?;
Ok(())
}
async fn create_bellande_user(username: &str, privilege: PrivilegeLevel) -> Result<()> {
let mut cmd = std::process::Command::new("bellctl");
cmd.args(&["user", "create", username]);
match privilege {
PrivilegeLevel::Administrator => {
cmd.arg("--admin");
}
PrivilegeLevel::Root => {
cmd.arg("--root");
}
PrivilegeLevel::Bell => {
cmd.arg("--bell");
}
_ => {}
}
cmd.output().context("Failed to create BellandeOS user")?;
Ok(())
}
async fn remove_macos_user(username: &str) -> Result<()> {
std::process::Command::new("sysadminctl")
.args(&["-deleteUser", username])
.output()
.context("Failed to remove macOS user")?;
Ok(())
}
async fn remove_linux_user(username: &str) -> Result<()> {
std::process::Command::new("userdel")
.args(&["-r", username]) // -r flag removes home directory and mail spool
.output()
.context("Failed to remove Linux user")?;
Ok(())
}
async fn remove_bellande_user(username: &str) -> Result<()> {
std::process::Command::new("bellctl")
.args(&["user", "remove", username])
.output()
.context("Failed to remove BellandeOS user")?;
Ok(())
}
async fn update_macos_password(username: &str, password: &str) -> Result<()> {
std::process::Command::new("dscl")
.args(&[".", "-passwd", &format!("/Users/{}", username), password])
.output()
.context("Failed to update macOS password")?;
Ok(())
}
async fn update_linux_password(username: &str, password: &str) -> Result<()> {
let passwd_input = format!("{}:{}", username, password);
let mut child = std::process::Command::new("chpasswd")
.stdin(std::process::Stdio::piped())
.spawn()
.context("Failed to spawn chpasswd")?;
if let Some(mut stdin) = child.stdin.take() {
stdin
.write_all(passwd_input.as_bytes())
.context("Failed to write to chpasswd stdin")?;
}
child.wait().context("Failed to wait for chpasswd")?;
Ok(())
}
async fn update_bellande_password(username: &str, password: &str) -> Result<()> {
let mut child = std::process::Command::new("bellctl")
.args(&["user", "set-password", username])
.stdin(std::process::Stdio::piped())
.spawn()
.context("Failed to spawn bellctl")?;
if let Some(mut stdin) = child.stdin.take() {
stdin
.write_all(password.as_bytes())
.context("Failed to set BellandeOS password")?;
}
child.wait().context("Failed to wait for bellctl")?;
Ok(())
}
async fn update_macos_privileges(username: &str, privilege: PrivilegeLevel) -> Result<()> {
match privilege {
PrivilegeLevel::Administrator | PrivilegeLevel::Root | PrivilegeLevel::Bell => {
std::process::Command::new("dseditgroup")
.args(&["-o", "edit", "-a", username, "-t", "user", "admin"])
.output()
.context("Failed to update macOS privileges")?;
}
_ => {
std::process::Command::new("dseditgroup")
.args(&["-o", "edit", "-d", username, "-t", "user", "admin"])
.output()
.context("Failed to update macOS privileges")?;
}
}
Ok(())
}
async fn update_linux_privileges(username: &str, privilege: PrivilegeLevel) -> Result<()> {
match privilege {
PrivilegeLevel::Administrator | PrivilegeLevel::Root => {
std::process::Command::new("usermod")
.args(&["-aG", "sudo", username])
.output()
.context("Failed to update Linux privileges")?;
}
PrivilegeLevel::Bell => {
std::process::Command::new("usermod")
.args(&["-aG", "sudo,adm,root", username])
.output()
.context("Failed to update Linux privileges")?;
}
_ => {
std::process::Command::new("deluser")
.args(&[username, "sudo"])
.output()
.context("Failed to update Linux privileges")?;
}
}
Ok(())
}
async fn update_bellande_privileges(username: &str, privilege: PrivilegeLevel) -> Result<()> {
let privilege_str = match privilege {
PrivilegeLevel::User => "user",
PrivilegeLevel::Group => "group",
PrivilegeLevel::Administrator => "admin",
PrivilegeLevel::Root => "root",
PrivilegeLevel::Bell => "bell",
};
std::process::Command::new("bellctl")
.args(&["user", "set-privilege", username, privilege_str])
.output()
.context("Failed to update BellandeOS privileges")?;
Ok(())
}
async fn add_os_user_to_group(username: &str, group: &str) -> Result<()> {
match std::env::consts::OS {
"macos" => {
std::process::Command::new("dseditgroup")
.args(&["-o", "edit", "-a", username, "-t", "user", group])
.output()
.context("Failed to add macOS user to group")?;
}
"linux" => {
std::process::Command::new("usermod")
.args(&["-aG", group, username])
.output()
.context("Failed to add Linux user to group")?;
}
"bellandeos" => {
std::process::Command::new("bellctl")
.args(&["user", "add-to-group", username, group])
.output()
.context("Failed to add BellandeOS user to group")?;
}
_ => {}
}
Ok(())
}
async fn remove_os_user_from_group(username: &str, group: &str) -> Result<()> {
match std::env::consts::OS {
"macos" => {
std::process::Command::new("dseditgroup")
.args(&["-o", "edit", "-d", username, "-t", "user", group])
.output()
.context("Failed to remove macOS user from group")?;
}
"linux" => {
std::process::Command::new("deluser")
.args(&[username, group])
.output()
.context("Failed to remove Linux user from group")?;
}
"bellandeos" => {
std::process::Command::new("bellctl")
.args(&["user", "remove-from-group", username, group])
.output()
.context("Failed to remove BellandeOS user from group")?;
}
_ => {}
}
Ok(())
}