├── CONTRIBUTING.md ├── ChangeLog ├── LICENSE ├── MANIFEST ├── Makefile.PL ├── README.md ├── doc └── pgdsat.pod ├── lib ├── PGDSAT.pm └── PGDSAT │ ├── Labels.pm │ ├── Messages.pm │ ├── Netmask.pm │ └── Report.pm ├── pgdsat ├── rsc ├── pgdsat-logo-small.png └── pgdsat-logo.png └── sample ├── report.html └── report.txt /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | ## Before Submitting an issue 4 | 5 | 1. Upgrade to the latest version of pgdsat and see if the problem remains 6 | 7 | 2. Look at the [closed issues](https://github.com/hexacluster/pgdsat/issues?state=closed), we may have already answered a similar problem 8 | 9 | 3. [Read the doc](http://github.com/hexacluster/pgdsat/). It is short and useful. 10 | 11 | ## Keep Documentation Updated 12 | 13 | pgdsat documentation can be obtained first with `pgdsat --help` 14 | The file `doc/pgdsat.pod` is the description of the utilities containing global 15 | information and usage of commands from `--help` option. 16 | 17 | The `README.md` is generated using: `pod2markdown doc/pgdsat.pod README.md` 18 | 19 | The man-page is generated automatically with the installation commands. 20 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2024-04-19 - Version 1.1 2 | 3 | This release fixes several issues reported by users since last release and adds 4 | some new features: 5 | 6 | * Add cluster version mismatch check if --cluster is used. 7 | * Add a check to ensure a data anonymization extension is installed 8 | (extensions searched in session_preload_libraries: pg_anonymize or anon). 9 | * Add check to ensure tablespace location is not inside the PGDATA. 10 | * Add statistics about checksum failures if any. 11 | * Double check the Unix socket permission on disk 12 | * Add check to ensure that the public schema is protected in all database. 13 | Thanks to Julien Rouhaud for the report. 14 | 15 | Here is the list of changes: 16 | 17 | - Fix check for versions shows success when using an unprivileged user. 18 | Thanks to Avinash Vallarapu for the report. 19 | - Fix incorrect status for Checksum check when user has insufficient 20 | permission to use the PGDATA. Thanks to Avinash Vallarapu for the patch. 21 | - Redirect ls command error to /dev/null for tablespace check. Thanks to 22 | Avinash Vallarapu for the report. 23 | - Fix typo in psql command. Thanks to Avinash Vallarapu for the report. 24 | - Mark last tablespace check as not from CIS Benchmark 25 | - Fix number of checks done by pgdsat in documentation 26 | - Verify that all necessary commands are available from $PATH. 27 | - Review the way collapse id is generated. 28 | - Verify at beginning that the connection user is really superuser. Thanks 29 | to Julien Rouhaud for the report. 30 | - Use pg_controldata to verify checksum instead of pg_checksums for 31 | performances reason. Thanks to Julien Rouhaud for the report. 32 | - Force use of -X with psql command to avoid looking at .psqlrc. Thanks to 33 | Julien Rouhaud for the patch. 34 | - Remove the source option, not implemented yet. 35 | 36 | 2024-03-25 - Initial version v1.0 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | 3 | Version 3, 29 June 2007 4 | 5 | Copyright © 2007 Free Software Foundation, Inc. 6 | 7 | Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for software and other kinds of works. 11 | 12 | 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. 13 | 14 | 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. 15 | 16 | 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. 17 | 18 | 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. 19 | 20 | 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. 21 | 22 | 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. 23 | 24 | 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. 25 | 26 | 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. 27 | 28 | The precise terms and conditions for copying, distribution and modification follow. 29 | TERMS AND CONDITIONS 30 | 0. Definitions. 31 | 32 | “This License” refers to version 3 of the GNU General Public License. 33 | 34 | “Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. 35 | 36 | “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. 37 | 38 | 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. 39 | 40 | A “covered work” means either the unmodified Program or a work based on the Program. 41 | 42 | 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. 43 | 44 | 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. 45 | 46 | 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. 47 | 1. Source Code. 48 | 49 | 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. 50 | 51 | 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. 52 | 53 | 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. 54 | 55 | 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. 56 | 57 | The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. 58 | 59 | The Corresponding Source for a work in source code form is that same work. 60 | 2. Basic Permissions. 61 | 62 | 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. 63 | 64 | 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. 65 | 66 | Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 67 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 68 | 69 | 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. 70 | 71 | 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. 72 | 4. Conveying Verbatim Copies. 73 | 74 | 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. 75 | 76 | 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. 77 | 5. Conveying Modified Source Versions. 78 | 79 | 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: 80 | 81 | a) The work must carry prominent notices stating that you modified it, and giving a relevant date. 82 | 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”. 83 | 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. 84 | 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. 85 | 86 | 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. 87 | 6. Conveying Non-Source Forms. 88 | 89 | 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: 90 | 91 | 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. 92 | 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. 93 | 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. 94 | 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. 95 | 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. 96 | 97 | 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. 98 | 99 | 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. 100 | 101 | “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. 102 | 103 | 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). 104 | 105 | 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. 106 | 107 | 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. 108 | 7. Additional Terms. 109 | 110 | “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. 111 | 112 | 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. 113 | 114 | 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: 115 | 116 | a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or 117 | 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 118 | 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 119 | d) Limiting the use for publicity purposes of names of licensors or authors of the material; or 120 | e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or 121 | 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. 122 | 123 | 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. 124 | 125 | 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. 126 | 127 | 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. 128 | 8. Termination. 129 | 130 | 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). 131 | 132 | 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. 133 | 134 | 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. 135 | 136 | 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. 137 | 9. Acceptance Not Required for Having Copies. 138 | 139 | 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. 140 | 10. Automatic Licensing of Downstream Recipients. 141 | 142 | 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. 143 | 144 | 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. 145 | 146 | 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. 147 | 11. Patents. 148 | 149 | 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”. 150 | 151 | 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. 152 | 153 | 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. 154 | 155 | 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. 156 | 157 | 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. 158 | 159 | 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. 160 | 161 | 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. 162 | 163 | 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. 164 | 12. No Surrender of Others' Freedom. 165 | 166 | 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. 167 | 13. Use with the GNU Affero General Public License. 168 | 169 | 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. 170 | 14. Revised Versions of this License. 171 | 172 | 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. 173 | 174 | 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. 175 | 176 | 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. 177 | 178 | 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. 179 | 15. Disclaimer of Warranty. 180 | 181 | 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. 182 | 16. Limitation of Liability. 183 | 184 | 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. 185 | 17. Interpretation of Sections 15 and 16. 186 | 187 | 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. 188 | 189 | END OF TERMS AND CONDITIONS 190 | How to Apply These Terms to Your New Programs 191 | 192 | 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. 193 | 194 | 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. 195 | 196 | 197 | Copyright (C) 198 | 199 | This program is free software: you can redistribute it and/or modify 200 | it under the terms of the GNU General Public License as published by 201 | the Free Software Foundation, either version 3 of the License, or 202 | (at your option) any later version. 203 | 204 | This program is distributed in the hope that it will be useful, 205 | but WITHOUT ANY WARRANTY; without even the implied warranty of 206 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 207 | GNU General Public License for more details. 208 | 209 | You should have received a copy of the GNU General Public License 210 | along with this program. If not, see . 211 | 212 | Also add information on how to contact you by electronic and paper mail. 213 | 214 | If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: 215 | 216 | Copyright (C) 217 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 218 | This is free software, and you are welcome to redistribute it 219 | under certain conditions; type `show c' for details. 220 | 221 | 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”. 222 | 223 | 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 . 224 | 225 | 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 . 226 | 227 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | LICENSE 2 | Makefile.PL 3 | MANIFEST 4 | pgdsat 5 | README.md 6 | doc/pgdsat.pod 7 | lib/PGDSAT/Labels.pm 8 | lib/PGDSAT/Messages.pm 9 | lib/PGDSAT/Report.pm 10 | lib/PGDSAT/Netmask.pm 11 | ChangeLog 12 | -------------------------------------------------------------------------------- /Makefile.PL: -------------------------------------------------------------------------------- 1 | use ExtUtils::MakeMaker; 2 | # See lib/ExtUtils/MakeMaker.pm for details of how to influence 3 | # the contents of the Makefile that is written. 4 | 5 | use strict; 6 | 7 | my @ALLOWED_ARGS = ('INSTALLDIRS','DESTDIR'); 8 | 9 | # Parse command line arguments and store them as environment variables 10 | while ($_ = shift) { 11 | my ($k,$v) = split(/=/, $_, 2); 12 | if (grep(/^$k$/, @ALLOWED_ARGS)) { 13 | $ENV{$k} = $v; 14 | } 15 | } 16 | $ENV{DESTDIR} =~ s/\/$//; 17 | 18 | # Default install path 19 | my $DESTDIR = $ENV{DESTDIR} || ''; 20 | my $INSTALLDIRS = $ENV{INSTALLDIRS} || 'site'; 21 | my %merge_compat = (); 22 | 23 | if ($ExtUtils::MakeMaker::VERSION >= 6.46) { 24 | %merge_compat = ( 25 | 'META_MERGE' => { 26 | resources => { 27 | homepage => 'https://github.com/hexacluster/pgdsat', 28 | repository => { 29 | type => 'git', 30 | git => 'git@github.com:hexacluster/pgdsat.git', 31 | web => 'https://github.com/hexacluster/pgdsat', 32 | }, 33 | }, 34 | } 35 | ); 36 | } 37 | 38 | WriteMakefile( 39 | 'DISTNAME' => 'pgdsat', 40 | 'NAME' => 'pgdsat', 41 | 'VERSION' => '1.1', 42 | 'LICENSE' => 'gpl_3', 43 | 'dist' => { 44 | 'COMPRESS'=>'gzip -9f', 'SUFFIX' => 'gz', 45 | 'ZIP'=>'/usr/bin/zip','ZIPFLAGS'=>'-rl' 46 | }, 47 | 'AUTHOR' => 'Gilles Darold (gilles@darold.net)', 48 | 'ABSTRACT' => 'pgdsat - PostgreSQL Database Security Assessment Tool', 49 | 'EXE_FILES' => [ qw(pgdsat) ], 50 | 'MAN1PODS' => { 'doc/pgdsat.pod' => 'blib/man1/pgdsat.1p' }, 51 | 'DESTDIR' => $DESTDIR, 52 | 'INSTALLDIRS' => $INSTALLDIRS, 53 | 'clean' => {FILES => 'lib/blib/'}, 54 | %merge_compat 55 | ); 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## NAME 2 | 3 | pgdsat - PostgreSQL Database Security Assessment Tool 4 | 5 | ## DESCRIPTION 6 | 7 | PGDSAT is a security assessment tool that checks around 70 PostgreSQL security 8 | controls of your PostgreSQL clusters including all recommendations from the 9 | [CIS compliance benchmark](https://www.cisecurity.org/cis-benchmarks/) 10 | but not only. 11 | 12 | This tool is a single command that must be run on the PostgreSQL server 13 | to collect all necessaries system and PostgreSQL information to compute a 14 | security assessment report. A report consist in a summary of all tests status 15 | and a second part with all detailed information. 16 | See [sample report](https://www.darold.net/sample_pgdsat/report.html). 17 | 18 | This PostgreSQL Security Assessment Tool allow assessments to be carried out 19 | in an automated manner to verify the security policies established inside 20 | the company. It also gives understanding of the security issued that your 21 | cluster can be faced. 22 | 23 | Although the default text output format can be read directly from a terminal 24 | the use of the HTML output format is recommended for better reading as you 25 | can see in the sample report above. 26 | 27 | ## SECURITY CHECKS 28 | 29 | All checks recommended by the CIS Benchmark for PostgreSQL 16 are implemented 30 | but not only. Some additional tests not part of the CIS document are marked with 31 | the "(\*)" mention in the description. 32 | 33 | Here is the list all checks performed on a PostgreSQL cluster. Some must be 34 | checked manually but most of them are check automatically by the tool. 35 | 36 | 1 - Installation and Patches 37 | 1.1 - Ensure packages are obtained from authorized repositories => SUCCESS 38 | 1.1.1 - PostgreSQL packages installed. (Manual) 39 | 1.1.2 - Ensure packages are obtained from PGDG => SUCCESS 40 | 1.2 - Ensure systemd Service Files Are Enabled => SUCCESS 41 | 1.3 - Ensure Data Cluster Initialized Successfully => SUCCESS 42 | 1.3.1 - Check initialization of the PGDATA => SUCCESS 43 | 1.3.2 - Check version in PGDATA => SUCCESS 44 | 1.3.3 - Ensure Data Cluster have checksum enabled => FAILURE 45 | 1.3.4 - Ensure WALs and temporary files are not on the same partition as the PGDATA => FAILURE 46 | 1.3.5 - Ensure that the PGDATA partition is encrypted (Manual) 47 | 1.4 - Ensure PostgreSQL versions are up-to-date => FAILURE 48 | 1.5 - Ensure unused PostgreSQL extensions are removed (Manual) 49 | 2 - Directory and File Permissions 50 | 2.1 - Ensure the file permissions mask is correct => FAILURE 51 | 2.2 - Check permissions of PGDATA => SUCCESS 52 | 2.3 - List content of PGDATA to check unwanted files and symlinks (Manual) 53 | 2.4 - Check permissions of pg_hba.conf => SUCCESS 54 | 2.5 - Check permissions on Unix Socket => FAILURE 55 | 3 - Logging And Auditing 56 | 3.1 - PostgreSQL Logging => SUCCESS 57 | 3.1.1 - Logging Rationale => SUCCESS 58 | 3.1.2 - Ensure the log destinations are set correctly => SUCCESS 59 | 3.1.3 - Ensure the logging collector is enabled => FAILURE 60 | 3.1.4 - Ensure the log file destination directory is set correctly => SUCCESS 61 | 3.1.5 - Ensure the filename pattern for log files is set correctly (Manual) 62 | 3.1.6 - Ensure the log file permissions are set correctly => SUCCESS 63 | 3.1.7 - Ensure 'log_truncate_on_rotation' is enabled => SUCCESS 64 | 3.1.8 - Ensure the maximum log file lifetime is set correctly (Manual) 65 | 3.1.9 - Ensure the maximum log file size is set correctly (Manual) 66 | 3.1.10 - Ensure the correct syslog facility is selected (Manual) 67 | 3.1.11 - Ensure syslog messages are not suppressed => SUCCESS 68 | 3.1.12 - Ensure syslog messages are not lost due to size => SUCCESS 69 | 3.1.13 - Ensure the program name for PostgreSQL syslog messages is correct (Manual) 70 | 3.1.14 - Ensure the correct messages are written to the server log => SUCCESS 71 | 3.1.15 - Ensure the correct SQL statements generating errors are recorded => SUCCESS 72 | 3.1.16 - Ensure 'debug_print_parse' is disabled => SUCCESS 73 | 3.1.17 - Ensure 'debug_print_rewritten' is disabled => SUCCESS 74 | 3.1.18 - Ensure 'debug_print_plan' is disabled => SUCCESS 75 | 3.1.19 - Ensure 'debug_pretty_print' is enabled => SUCCESS 76 | 3.1.20 - Ensure 'log_connections' is enabled => FAILURE 77 | 3.1.21 - Ensure 'log_disconnections' is enabled => FAILURE 78 | 3.1.22 - Ensure 'log_error_verbosity' is set correctly => FAILURE 79 | 3.1.23 - Ensure 'log_hostname' is set correctly => SUCCESS 80 | 3.1.24 - Ensure 'log_line_prefix' is set correctly => FAILURE 81 | 3.1.25 - Ensure 'log_statement' is set correctly => FAILURE 82 | 3.1.26 - Ensure 'log_timezone' is set correctly => FAILURE 83 | 3.1.27 - Ensure that log_directory is outside the PGDATA => SUCCESS 84 | 3.2 - Ensure the PostgreSQL Audit Extension (pgAudit) is enabled => SUCCESS 85 | 4 - User Access and Authorization 86 | 4.1 - Ensure sudo is configured correctly (Manual) 87 | 4.2 - Ensure excessive administrative privileges are revoked => FAILURE 88 | 4.3 - Ensure excessive function privileges are revoked (Manual) 89 | 4.4 - Ensure excessive DML privileges are revoked (Manual) 90 | 4.5 - Ensure Row Level Security (RLS) is configured correctly (Manual) 91 | 4.6 - Ensure the set_user extension is installed (Manual) => FAILURE 92 | 4.7 - Make use of predefined roles (Manual) 93 | 4.8 - Ensuse the public schema is protected 94 | 5 - Connection and Login 95 | 5.1 - Ensure login via "local" UNIX Domain Socket is configured correctly => FAILURE 96 | 5.2 - Ensure login via "host" TCP/IP Socket is configured correctly => SUCCESS 97 | 5.3 - Ensure Password Complexity is configured => SUCCESS 98 | 5.4 - Ensure authentication timeout and delay are well configured => FAILURE 99 | 5.5 - Ensure SSL is used for client connection => FAILURE 100 | 5.6 - Ensure authorized Ip addresses ranges are not too large => SUCCESS 101 | 5.7 - Ensure specific database and users are used => FAILURE 102 | 5.8 - Ensure superusers are not allowed to connect remotely => SUCCESS 103 | 5.9 - Ensure that 'password_encryption' is correctly set => SUCCESS 104 | 6 - PostgreSQL Settings 105 | 6.1 - Understanding attack vectors and runtime parameters 106 | 6.2 - Ensure 'backend' runtime parameters are configured correctly => FAILURE 107 | 6.3 - Ensure 'Postmaster' runtime parameters are configured correctly (Manual) 108 | 6.4 - Ensure 'SIGHUP' runtime parameters are configured correctly (Manual) 109 | 6.5 - Ensure 'Superuser' runtime parameters are configured correctly (Manual) 110 | 6.6 - Ensure 'User' runtime parameters are configured correctly (Manual) 111 | 6.7 - Ensure FIPS 140-2 OpenSSL cryptography is used => FAILURE 112 | 6.8 - Ensure TLS is enabled and configured correctly => FAILURE 113 | 6.9 - Ensure a cryptographic extension is installed => SUCCESS 114 | 7 - Replication 115 | 7.1 - Ensure a replication-only user is created and used for streaming replication => FAILURE 116 | 7.2 - Ensure logging of replication commands is configured => FAILURE 117 | 7.3 - Ensure base backups are configured and functional => SUCCESS 118 | 7.4 - Ensure WAL archiving is configured and functional => FAILURE 119 | 7.5 - Ensure streaming replication parameters are configured correctly => FAILURE 120 | 8 - Special Configuration Considerations 121 | 8.1 - Ensure PostgreSQL subdirectory locations are outside the data cluster => SUCCESS 122 | 8.2 - Ensure the backup and restore tool, 'pgBackRest', is installed and configured => FAILURE 123 | 8.3 - Ensure miscellaneous configuration settings are correct (Manual) 124 | 125 | ## REQUIREMENT 126 | 127 | PGDSAT is a standalone program that can be run on any Linux server, it doesn't require 128 | any additional package installation except if your system do not have the following 129 | requirements: 130 | 131 | - Ensure that PostgreSQL binaries are reachable from the '$PATH' environment variable. 132 | - Ensure PostgreSQL >= 10. 133 | - Ensure that 'PGHOST', 'PGUSER', 'PGPASSWORD' and 'PGDATA' environment variables are defined or use the dedicated command line options (except for 'PGPASSWORD'). Or set a '.pgpass' file in the postgres system account. 134 | - Ensure 'sudo apt install crypto-policies' have been run for the FIPS test. 135 | - Ensure the Perl bignum module is installed 'perl -e "use bignum"', if the command fail, 136 | install it using 'dnf install perl-bignum perl-Math-BigRat'. 137 | - Ensure the 'curl' command is available and the server have access to Internet. It is used to check the PostgreSQL version online. If this is not possible, use command line option --no-pg-version-check to disable this check. 138 | - To view the HTML report you also need an Internet access to obtain the FontAwesome icons used in the Summary Table of security checks. 139 | 140 | ## INSTALLATION 141 | 142 | The PostgreSQL command psql is used to query the PostgreSQL cluster. 143 | 144 | To install PGDSAT: 145 | 146 | perl Makefile.PL 147 | make 148 | sudo make install 149 | 150 | If you don't want to install PGDSAT on your system but just want to execute 151 | it from the source directory, follow the instruction at end of next chapter. 152 | 153 | Some Linux RPM based distributions do not provide the bignum Perl module by 154 | default, you will need to install it: 155 | 156 | dnf install perl-bignum perl-Math-BigRat 157 | 158 | ## USAGE 159 | 160 | PGSAT use commands to look at the system and especially to the PostgreSQL 161 | installation. It means that it requires the privilege of owner of these 162 | repository to be executed. Run it as postgres system user. 163 | 164 | Usage: pgdsat \[options\] 165 | 166 | PostgreSQL Database Security Assessment Tool. 167 | 168 | Options: 169 | 170 | -a | --allow : database to include into the report in parts 4.3 to 4.5. 171 | Can be used multiple time and regexp are supported. 172 | -d | --database: name of the database to connect to PostgreSQL. 173 | -D | --pgdata : path to the PostgreSQL cluster PGDATA to analyze. 174 | -e | --exclude : database to exclude from the report in parts 4.3 to 4.5. 175 | Can be used multiple time and regexp are supported. 176 | -f | --format : output format, can be: text or html. Default: html. 177 | -h | --host : PostgreSQL serveur ip address if not listening on localhost 178 | -l | --lang : language used for the output (en_US, fr_FR, zh_CN). Default: en_US 179 | -o | --output : output file where to write the report. Default stdout. 180 | -p | --port : port where PostgreSQL is listening, default: 5432. 181 | -P | --psql : full path to the psql command if not found in PATH. 182 | -r | --remove : check to remove from the report, it can be used multiple 183 | time. The value can be the number of a check or a regexp. 184 | -T | --title : set title to use to differentiate the reports. Default is 185 | to use "on `hostname`". 186 | -U | --user : PostgreSQL user to use with the psql command. 187 | -v | --version : show version of pgdsat and exist. 188 | -V | --cluster : PostgreSQL Cluster version, ex: 15.4. 189 | --help : show usage and exit. 190 | 191 | --no-pg-version-check : disable check for PostgreSQL minor versions. Useful 192 | when connecting to Internet is not permitted. 193 | Example: 194 | 195 | pgdsat -U postgres -h localhost -d postgres -o report.html 196 | or 197 | pgdsat -U postgres -h localhost -d postgres -f html > report.html 198 | 199 | If you have several PostgreSQL cluster installed you must give the running 200 | version that you want to test: 201 | 202 | pgdsat -U postgres -h localhost -d postgres -f html -V 15.4 > report.html 203 | 204 | If you want, for example, to remove all checks of section 1 from the report: 205 | 206 | pgdsat -U postgres -h localhost -d postgres -V 15.4 -o report.html -r '1.*' 207 | 208 | To execute the pgdsat command locally without installation, use: 209 | 210 | sudo perl -I ./lib ./pgdsat ... 211 | 212 | ## AUTHORS 213 | 214 | - Gilles Darold. 215 | 216 | ## LICENSE 217 | 218 | pgdsat is free software distributed under the GPLv3 license. See LICENCE file for more information. 219 | 220 | Copyright (c) 2024 HexaCluster Corp 221 | 222 | Some parts are copied from the [CIS Benchmark](https://www.cisecurity.org/cis-benchmarks) 223 | licensed under the [Creative Common Version 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode) 224 | terms of use. 225 | -------------------------------------------------------------------------------- /doc/pgdsat.pod: -------------------------------------------------------------------------------- 1 | =head2 NAME 2 | 3 | pgdsat - PostgreSQL Database Security Assessment Tool 4 | 5 | =head2 DESCRIPTION 6 | 7 | PGDSAT is a security assessment tool that checks around 70 PostgreSQL security 8 | controls of your PostgreSQL clusters including all recommendations from the 9 | L 10 | but not only. 11 | 12 | This tool is a single command that must be run on the PostgreSQL server 13 | to collect all necessaries system and PostgreSQL information to compute a 14 | security assessment report. A report consist in a summary of all tests status 15 | and a second part with all detailed information. 16 | See L. 17 | 18 | This PostgreSQL Security Assessment Tool allow assessments to be carried out 19 | in an automated manner to verify the security policies established inside 20 | the company. It also gives understanding of the security issued that your 21 | cluster can be faced. 22 | 23 | Although the default text output format can be read directly from a terminal 24 | the use of the HTML output format is recommended for better reading as you 25 | can see in the sample report above. 26 | 27 | =head2 SECURITY CHECKS 28 | 29 | All checks recommended by the CIS Benchmark for PostgreSQL 16 are implemented 30 | but not only. Some additional tests not part of the CIS document are marked with 31 | the "(*)" mention in the description. 32 | 33 | Here is the list all checks performed on a PostgreSQL cluster. Some must be 34 | checked manually but most of them are check automatically by the tool. 35 | 36 | 1 - Installation and Patches 37 | 1.1 - Ensure packages are obtained from authorized repositories => SUCCESS 38 | 1.1.1 - PostgreSQL packages installed. (Manual) 39 | 1.1.2 - Ensure packages are obtained from PGDG => SUCCESS 40 | 1.2 - Ensure systemd Service Files Are Enabled => SUCCESS 41 | 1.3 - Ensure Data Cluster Initialized Successfully => SUCCESS 42 | 1.3.1 - Check initialization of the PGDATA => SUCCESS 43 | 1.3.2 - Check version in PGDATA => SUCCESS 44 | 1.3.3 - Ensure Data Cluster have checksum enabled => FAILURE 45 | 1.3.4 - Ensure WALs and temporary files are not on the same partition as the PGDATA => FAILURE 46 | 1.3.5 - Ensure that the PGDATA partition is encrypted (Manual) 47 | 1.4 - Ensure PostgreSQL versions are up-to-date => FAILURE 48 | 1.5 - Ensure unused PostgreSQL extensions are removed (Manual) 49 | 2 - Directory and File Permissions 50 | 2.1 - Ensure the file permissions mask is correct => FAILURE 51 | 2.2 - Check permissions of PGDATA => SUCCESS 52 | 2.3 - List content of PGDATA to check unwanted files and symlinks (Manual) 53 | 2.4 - Check permissions of pg_hba.conf => SUCCESS 54 | 2.5 - Check permissions on Unix Socket => FAILURE 55 | 3 - Logging And Auditing 56 | 3.1 - PostgreSQL Logging => SUCCESS 57 | 3.1.1 - Logging Rationale => SUCCESS 58 | 3.1.2 - Ensure the log destinations are set correctly => SUCCESS 59 | 3.1.3 - Ensure the logging collector is enabled => FAILURE 60 | 3.1.4 - Ensure the log file destination directory is set correctly => SUCCESS 61 | 3.1.5 - Ensure the filename pattern for log files is set correctly (Manual) 62 | 3.1.6 - Ensure the log file permissions are set correctly => SUCCESS 63 | 3.1.7 - Ensure 'log_truncate_on_rotation' is enabled => SUCCESS 64 | 3.1.8 - Ensure the maximum log file lifetime is set correctly (Manual) 65 | 3.1.9 - Ensure the maximum log file size is set correctly (Manual) 66 | 3.1.10 - Ensure the correct syslog facility is selected (Manual) 67 | 3.1.11 - Ensure syslog messages are not suppressed => SUCCESS 68 | 3.1.12 - Ensure syslog messages are not lost due to size => SUCCESS 69 | 3.1.13 - Ensure the program name for PostgreSQL syslog messages is correct (Manual) 70 | 3.1.14 - Ensure the correct messages are written to the server log => SUCCESS 71 | 3.1.15 - Ensure the correct SQL statements generating errors are recorded => SUCCESS 72 | 3.1.16 - Ensure 'debug_print_parse' is disabled => SUCCESS 73 | 3.1.17 - Ensure 'debug_print_rewritten' is disabled => SUCCESS 74 | 3.1.18 - Ensure 'debug_print_plan' is disabled => SUCCESS 75 | 3.1.19 - Ensure 'debug_pretty_print' is enabled => SUCCESS 76 | 3.1.20 - Ensure 'log_connections' is enabled => FAILURE 77 | 3.1.21 - Ensure 'log_disconnections' is enabled => FAILURE 78 | 3.1.22 - Ensure 'log_error_verbosity' is set correctly => FAILURE 79 | 3.1.23 - Ensure 'log_hostname' is set correctly => SUCCESS 80 | 3.1.24 - Ensure 'log_line_prefix' is set correctly => FAILURE 81 | 3.1.25 - Ensure 'log_statement' is set correctly => FAILURE 82 | 3.1.26 - Ensure 'log_timezone' is set correctly => FAILURE 83 | 3.1.27 - Ensure that log_directory is outside the PGDATA => SUCCESS 84 | 3.2 - Ensure the PostgreSQL Audit Extension (pgAudit) is enabled => SUCCESS 85 | 4 - User Access and Authorization 86 | 4.1 - Ensure sudo is configured correctly (Manual) 87 | 4.2 - Ensure excessive administrative privileges are revoked => FAILURE 88 | 4.3 - Ensure excessive function privileges are revoked (Manual) 89 | 4.4 - Ensure excessive DML privileges are revoked (Manual) 90 | 4.5 - Ensure Row Level Security (RLS) is configured correctly (Manual) 91 | 4.6 - Ensure the set_user extension is installed (Manual) => FAILURE 92 | 4.7 - Make use of predefined roles (Manual) 93 | 4.8 - Ensuse the public schema is protected 94 | 5 - Connection and Login 95 | 5.1 - Ensure login via "local" UNIX Domain Socket is configured correctly => FAILURE 96 | 5.2 - Ensure login via "host" TCP/IP Socket is configured correctly => SUCCESS 97 | 5.3 - Ensure Password Complexity is configured => SUCCESS 98 | 5.4 - Ensure authentication timeout and delay are well configured => FAILURE 99 | 5.5 - Ensure SSL is used for client connection => FAILURE 100 | 5.6 - Ensure authorized Ip addresses ranges are not too large => SUCCESS 101 | 5.7 - Ensure specific database and users are used => FAILURE 102 | 5.8 - Ensure superusers are not allowed to connect remotely => SUCCESS 103 | 5.9 - Ensure that 'password_encryption' is correctly set => SUCCESS 104 | 6 - PostgreSQL Settings 105 | 6.1 - Understanding attack vectors and runtime parameters 106 | 6.2 - Ensure 'backend' runtime parameters are configured correctly => FAILURE 107 | 6.3 - Ensure 'Postmaster' runtime parameters are configured correctly (Manual) 108 | 6.4 - Ensure 'SIGHUP' runtime parameters are configured correctly (Manual) 109 | 6.5 - Ensure 'Superuser' runtime parameters are configured correctly (Manual) 110 | 6.6 - Ensure 'User' runtime parameters are configured correctly (Manual) 111 | 6.7 - Ensure FIPS 140-2 OpenSSL cryptography is used => FAILURE 112 | 6.8 - Ensure TLS is enabled and configured correctly => FAILURE 113 | 6.9 - Ensure a cryptographic extension is installed => SUCCESS 114 | 7 - Replication 115 | 7.1 - Ensure a replication-only user is created and used for streaming replication => FAILURE 116 | 7.2 - Ensure logging of replication commands is configured => FAILURE 117 | 7.3 - Ensure base backups are configured and functional => SUCCESS 118 | 7.4 - Ensure WAL archiving is configured and functional => FAILURE 119 | 7.5 - Ensure streaming replication parameters are configured correctly => FAILURE 120 | 8 - Special Configuration Considerations 121 | 8.1 - Ensure PostgreSQL subdirectory locations are outside the data cluster => SUCCESS 122 | 8.2 - Ensure the backup and restore tool, 'pgBackRest', is installed and configured => FAILURE 123 | 8.3 - Ensure miscellaneous configuration settings are correct (Manual) 124 | 125 | =head2 REQUIREMENT 126 | 127 | PGDSAT is a standalone program that can be run on any Linux server, it doesn't require 128 | any additional package installation except if your system do not have the following 129 | requirements: 130 | 131 | =over 132 | 133 | =item * 134 | 135 | Ensure that PostgreSQL binaries are reachable from the '$PATH' environment variable. 136 | 137 | =item * 138 | 139 | Ensure PostgreSQL >= 10. 140 | 141 | =item * 142 | 143 | Ensure that 'PGHOST', 'PGUSER', 'PGPASSWORD' and 'PGDATA' environment variables are defined or use the dedicated command line options (except for 'PGPASSWORD'). Or set a '.pgpass' file in the postgres system account. 144 | 145 | =item * 146 | 147 | Ensure 'sudo apt install crypto-policies' have been run for the FIPS test. 148 | 149 | =item * 150 | 151 | Ensure the Perl bignum module is installed 'perl -e "use bignum"', if the command fail, 152 | install it using 'dnf install perl-bignum perl-Math-BigRat'. 153 | 154 | =item * 155 | 156 | Ensure the 'curl' command is available and the server have access to Internet. It is used to check the PostgreSQL version online. If this is not possible, use command line option --no-pg-version-check to disable this check. 157 | 158 | =item * 159 | 160 | To view the HTML report you also need an Internet access to obtain the FontAwesome icons used in the Summary Table of security checks. 161 | 162 | =back 163 | 164 | =head2 INSTALLATION 165 | 166 | The PostgreSQL command psql is used to query the PostgreSQL cluster. 167 | 168 | To install PGDSAT: 169 | 170 | perl Makefile.PL 171 | make 172 | sudo make install 173 | 174 | If you don't want to install PGDSAT on your system but just want to execute 175 | it from the source directory, follow the instruction at end of next chapter. 176 | 177 | Some Linux RPM based distributions do not provide the bignum Perl module by 178 | default, you will need to install it: 179 | 180 | dnf install perl-bignum perl-Math-BigRat 181 | 182 | 183 | =head2 USAGE 184 | 185 | PGSAT use commands to look at the system and especially to the PostgreSQL 186 | installation. It means that it requires the privilege of owner of these 187 | repository to be executed. Run it as postgres system user. 188 | 189 | Usage: pgdsat [options] 190 | 191 | PostgreSQL Database Security Assessment Tool. 192 | 193 | Options: 194 | 195 | -a | --allow : database to include into the report in parts 4.3 to 4.5. 196 | Can be used multiple time and regexp are supported. 197 | -d | --database: name of the database to connect to PostgreSQL. 198 | -D | --pgdata : path to the PostgreSQL cluster PGDATA to analyze. 199 | -e | --exclude : database to exclude from the report in parts 4.3 to 4.5. 200 | Can be used multiple time and regexp are supported. 201 | -f | --format : output format, can be: text or html. Default: html. 202 | -h | --host : PostgreSQL serveur ip address if not listening on localhost 203 | -l | --lang : language used for the output (en_US, fr_FR, zh_CN). Default: en_US 204 | -o | --output : output file where to write the report. Default stdout. 205 | -p | --port : port where PostgreSQL is listening, default: 5432. 206 | -P | --psql : full path to the psql command if not found in PATH. 207 | -T | --title : set title to use to differentiate the reports. Default is 208 | to use "on `hostname`". 209 | -U | --user : PostgreSQL user to use with the psql command. 210 | -v | --version : show version of pgdsat and exist. 211 | -V | --cluster : PostgreSQL Cluster version, ex: 15.4. 212 | --help : show usage and exit. 213 | 214 | --no-pg-version-check : disable check for PostgreSQL minor versions. Useful 215 | when connecting to Internet is not permitted. 216 | Example: 217 | 218 | pgdsat -U postgres -h localhost -d postgres -o report.html 219 | or 220 | pgdsat -U postgres -h localhost -d postgres -f html > report.html 221 | 222 | If you have several PostgreSQL cluster installed you must give the running 223 | version that you want to test: 224 | 225 | pgdsat -U postgres -h localhost -d postgres -f html -V 15.4 > report.html 226 | 227 | To execute the pgdsat command locally without installation, use: 228 | 229 | sudo perl -I ./lib ./pgdsat ... 230 | 231 | =head2 AUTHORS 232 | 233 | =over 234 | 235 | =item * 236 | 237 | Gilles Darold. 238 | 239 | =back 240 | 241 | =head2 LICENSE 242 | 243 | pgdsat is free software distributed under the GPLv3 license. See LICENCE file for more information. 244 | 245 | Copyright (c) 2024 HexaCluster Corp 246 | 247 | Some parts are copied from the L 248 | licensed under the L 249 | terms of use. 250 | 251 | -------------------------------------------------------------------------------- /lib/PGDSAT/Messages.pm: -------------------------------------------------------------------------------- 1 | package PGDSAT::Messages; 2 | #------------------------------------------------------------------------------ 3 | # Project : PostgreSQL Database Security Assement Tool 4 | # Name : PGDSAT/Messages.pm 5 | # Language : Perl 6 | # Authors : Gilles Darold 7 | # Copyright: Copyright (c) 2024 HexaCluster Corp 8 | # Function : Language module to import the securiy check description 9 | #------------------------------------------------------------------------------ 10 | use vars qw($VERSION %AUDIT_MSG); 11 | use strict; 12 | 13 | $VERSION = '1.1'; 14 | 15 | # The numbering here try to follow the numbering in the PGDSAT::Labels file 16 | # but it is not requires. We just keep the numbering from PGDSAT::Labels 17 | # to easily find the relation between the messages and the tests. 18 | 19 | %AUDIT_MSG = ( 20 | 'en_US' => { 21 | '0.1' => { 'errmsg' => 'Test passed'}, 22 | '1.1' => { 'errmsg' => 'No PostgreSQL packages found.' }, 23 | '1.2' => { 'errmsg' => 'PostgreSQL packages are not from the PGDG repository.' }, 24 | '1.3' => { 'errmsg' => 'No internet access to https://www.postgresql.org/.' }, 25 | '1.4' => { 'errmsg' => 'This PostgreSQL version, v%s, is no more supported.' }, 26 | '1.5' => { 'errmsg' => 'This PostgreSQL version, v%s, is not the last one of this branch (%s)' }, 27 | '1.6' => { 'errmsg' => 'See Why upgrade.' }, 28 | '1.7' => { 'errmsg' => 'PostgreSQL version %s, is not enabled as a systemd service.' }, 29 | '1.8' => { 'errmsg' => 'PostgreSQL systemd service must not be enabled when patroni is used.' }, 30 | '1.9' => { 'errmsg' => 'Wrong or no base directory found, the PGDATA (%s) must be initialized first (see initdb).' }, 31 | '1.10' => { 'errmsg' => 'The version of the PGDATA (%s) does not correspond to the PostgreSQL cluster version; You need to upgrade the PGDATA v%s to v%s first.' }, 32 | '1.11' => { 'errmsg' => 'Checksum are not enabled in PGDATA %s.' }, 33 | '1.12' => { 'errmsg' => 'Subdirectory pg_wal is not on a separate partition than the PGDATA %s.' }, 34 | '1.13' => { 'errmsg' => 'Subdirectory for temporary file is not on a separate partition than the PGDATA.' }, 35 | '1.14' => { 'errmsg' => 'Can not get information about encrypted partition, command lsblk is missing on this host.' }, 36 | '1.15' => { 'errmsg' => 'PostgreSQL version check was disabled (--no-pg-version-check) can not look for minor version upgrade.' }, 37 | '1.16' => { 'errmsg' => 'Tablespace location %s should not be inside the data directory.' }, 38 | '1.17' => { 'errmsg' => 'PostgreSQL version %s, is enabled as a systemd service.' }, 39 | '2.1' => { 'errmsg' => 'The umask must be 0077 or more restrictive for the postgres user. Currently it is set to %s.' }, 40 | '2.2' => { 'errmsg' => 'Permissions of the PGDATA are not secure: %s, must be drwx------.' }, 41 | '2.4' => { 'errmsg' => 'Permissions of the pg_hba.conf file (%s) are not secure: %s, must be -rw-r----- or -rw-------.' }, 42 | '2.5' => { 'errmsg' => 'Permission on Unix socket %s should be more restrictive, for example: 0770 or 0700. Currently it is set to 0777.' }, 43 | '3.1' => { 'errmsg' => 'Setting \'log_destination\' is not set, logging will be lost.' }, 44 | '3.2' => { 'errmsg' => 'Setting \'logging_collector\' should be enabled instead of using syslog.' }, 45 | '3.3' => { 'errmsg' => 'Setting \'logging_collector\' must be enabled when \'log_destination\' is not set to syslog, logging will be lost.' }, 46 | '3.4' => { 'errmsg' => 'Setting \'log_directory\' must be set, currently writes will be done in / and logging will be lost.' }, 47 | '3.5' => { 'errmsg' => 'Setting \'log_filename\' must be set, currently logging will be lost.' }, 48 | '3.6' => { 'errmsg' => 'Setting \'log_file_mode\' should be set to \'0600\', current value is %s.' }, 49 | '3.7' => { 'errmsg' => 'Setting \'log_truncate_on_rotation\' should be enabled.' }, 50 | '3.11' => { 'errmsg' => 'Setting \'syslog_sequence_numbers\' should be enabled, some messages can be lost.' }, 51 | '3.12' => { 'errmsg' => 'Setting \'syslog_split_messages\' should be enabled, some messages can be truncated.' }, 52 | '3.14' => { 'errmsg' => 'Setting \'log_min_messages\' should be set to \'warning\' to avoid tracing too many or too few messages.' }, 53 | '3.15' => { 'errmsg' => 'Setting \'log_min_error_statement\' should be set to \'error\' to avoid tracing too many or too few messages.' }, 54 | '3.16' => { 'errmsg' => 'Setting \'debug_print_parse\' should be disabled.' }, 55 | '3.17' => { 'errmsg' => 'Setting \'debug_print_rewritten\' should be disabled.' }, 56 | '3.18' => { 'errmsg' => 'Setting \'debug_print_plan\' should be disabled.' }, 57 | '3.19' => { 'errmsg' => 'Setting \'debug_pretty_print\' should be enabled.' }, 58 | '3.20' => { 'errmsg' => 'Setting \'log_connections\' should be enabled.' }, 59 | '3.21' => { 'errmsg' => 'Setting \'log_disconnections\' should be enabled.' }, 60 | '3.22' => { 'errmsg' => 'Setting \'log_error_verbosity\' should be set to \'verbose\'.' }, 61 | '3.23' => { 'errmsg' => 'Setting \'log_hostname\' should be disabled.' }, 62 | '3.24' => { 'errmsg' => 'Setting \'log_line_prefix\' should containt at least \'%%m [%%p]: db=%%d,user=%%u,app=%%a,client=%%h \' (for stderr logging). For syslog logging, the prefix should include \'user=%%u,db=%%d,app=%%a,client=%%h \'.' }, 63 | '3.25' => { 'errmsg' => 'Setting \'log_statement\' should at least be set to \'ddl\'.' }, 64 | '3.26' => { 'errmsg' => 'Setting \'log_timezone\' should be set to \'GMT\' or \'UTC\'.' }, 65 | '3.27' => { 'errmsg' => 'Setting \'log_directory\' should use a location that is not in the PGDATA.' }, 66 | '3.28' => { 'errmsg' => 'PostgreSQL extension pgAudit should be used.' }, 67 | '3.29' => { 'errmsg' => 'PostgreSQL extension pgAudit is not well configured, \'pgaudit.log\' setting shoud contain \'ddl\' and \'write\'.' }, 68 | '4.2' => { 'errmsg' => 'There are more than one PostgreSQL superuser.' }, 69 | '4.5' => { 'errmsg' => 'Some PostgreSQL user have Bypass RLS enabled.' }, 70 | '4.8' => { 'errmsg' => 'Schema public can be used by anyone in database %s.' }, 71 | '5.1' => { 'errmsg' => 'Can not find pg_hba.conf file "%s".' }, 72 | '5.2' => { 'errmsg' => 'Can not read pg_hba.conf file "%s", reason: "%s".' }, 73 | '5.3' => { 'errmsg' => 'Can not open directory "%s", reason: "%s".' }, 74 | '5.4' => { 'errmsg' => 'The use of the "%s" authentication method must not be used. See line %s in file %s.' }, 75 | '5.5' => { 'errmsg' => 'The use of the "md5" authentication method is vulnerable to packet replay attacks. See line %s in file %s.' }, 76 | '5.6' => { 'errmsg' => 'The use of the "ident" authentication method is insecure, the client running the ident server should be considered as untrust. See line %s in file %s.' }, 77 | '5.7' => { 'errmsg' => 'Use %s or any of the external authentication method (gss, sspi, pam, ldap, radius or cert) instead.' }, 78 | '5.8' => { 'errmsg' => 'No password difficulty enforcement library used. Consider using the credcheck or passwordcheck PostgreSQL extension.' }, 79 | '5.9' => { 'errmsg' => 'Setting \'authentication_timeout\' should be <= 60s.' }, 80 | '5.10' => { 'errmsg' => 'You should add an authentication failure delay to prevent brute force attack. See PostgreSQL extension credcheck or auth_delay.' }, 81 | '5.11' => { 'errmsg' => 'The use of the "host" connection type should be rejected when "hostssl" or "hostgssenc" is used. See line(s) %s in pg_hba.conf.' }, 82 | '5.12' => { 'errmsg' => 'Use of ssl encryption for all remote connection should be used, see "hostssl" and "hostgssenc" connection type.' }, 83 | '5.13' => { 'errmsg' => 'The use of %s \'%s\' correspond to any source. See line %s in file %s.' }, 84 | '5.14' => { 'errmsg' => 'The use of %s \'%s\' correspond to a too huge Ip range. See line %s in file %s.' }, 85 | '5.15' => { 'errmsg' => 'You should be more specific and give the database and users allowed to connect, not "all". See line %s in file %s.' }, 86 | '5.16' => { 'errmsg' => 'You should not allow superusers to connect remotely, only from local and peer authentication. See line %s in file %s.' }, 87 | '5.17' => { 'errmsg' => 'parameter \'password_encryption\' should be set to \'scram-sha-256\', not \'%s\'.' }, 88 | '6.2' => { 'errmsg' => 'Setting \'%s\' must be disabled.' }, 89 | '6.3' => { 'errmsg' => 'Setting \'%s\' must be enabled.' }, 90 | '6.4' => { 'errmsg' => 'Setting \'post_auth_delay\' must be set to 0.' }, 91 | '6.5' => { 'errmsg' => 'Installation of FIPS modules is not completed.' }, 92 | '6.6' => { 'errmsg' => 'See "switching the system to fips mode" to enable FIPS mode' }, 93 | '6.7' => { 'errmsg' => 'TLS is not enabled. Setting \'ssl\' should be activated.' }, 94 | '6.8' => { 'errmsg' => 'Setting \'ssl_min_protocol_version\' should be TLS v1.3 or newer.' }, 95 | '6.9' => { 'errmsg' => 'The SSL certificate should have a passphrase and setting \'ssl_passphrase_command\' should be set.' }, 96 | '6.10' => { 'errmsg' => 'To enforce TLS authentication for the server, appropriate "hostssl" or "hostgssenc" records must be added to the pg_hba.conf file and "host" connections rejected.' }, 97 | '6.11' => { 'errmsg' => 'Extensions pgcrypto or pgsodium are not installed.' }, 98 | '6.12' => { 'errmsg' => 'Extensions pg_anonymize or anon are not installed.' }, 99 | '7.1' => { 'errmsg' => 'A replication-only user should be created.' }, 100 | '7.2' => { 'errmsg' => 'Setting \'log_replication_commands\' should be enabled.' }, 101 | '7.4' => { 'errmsg' => 'WAL archiving is not activated. Setting \'archive_mode\' must be enabled.' }, 102 | '7.5' => { 'errmsg' => 'Settings \'archive_command\' or \'archive_library\' must be enabled.' }, 103 | '7.6' => { 'errmsg' => 'Setting \'primary_conninfo\' must enforce TLS encryption of the replication (sslmode=required).' }, 104 | '7.7' => { 'errmsg' => 'Setting \'primary_conninfo\' should enable SSL compression (sslcompression=1).' }, 105 | '8.2' => { 'errmsg' => 'The backup tool \'pgBackRest\' is not installed.' }, 106 | '8.3' => { 'errmsg' => 'No stanzas exist for \'pgBackRest\'.' }, 107 | 108 | }, 109 | #----------------------------------------------------------------------------- 110 | 'fr_FR' => { 111 | #----------------------------------------------------------------------------- 112 | '0.1' => { 'errmsg' => 'Test réussi'}, 113 | '1.1' => { 'errmsg' => 'Aucun paquet PostgreSQL trouvé.' }, 114 | '1.2' => { 'errmsg' => 'Les packages PostgreSQL ne proviennent pas du référentiel PGDG.' }, 115 | '1.3' => { 'errmsg' => 'Pas d\'accès Internet à https://www.postgresql.org/.' }, 116 | '1.4' => { 'errmsg' => 'Cette version de PostgreSQL, v%s, n\'est plus prise en charge.' }, 117 | '1.5' => { 'errmsg' => 'Cette version de PostgreSQL, v%s, n\'est pas la dernière de cette branche (%s)' }, 118 | '1.6' => { 'errmsg' => 'Voir Pourquoi mettre à niveau .' }, 119 | '1.7' => { 'errmsg' => 'La version %s de PostgreSQL n\'est pas activée en tant que service systemd.' }, 120 | '1.8' => { 'errmsg' => 'Le service systemd PostgreSQL ne doit pas être activé lorsque patroni est utilisé.' }, 121 | '1.9' => { 'errmsg' => 'Mauvais ou aucun répertoire de base trouvé, le PGDATA (%s) doit d\'abord être initialisé (voir initdb).' }, 122 | '1.10' => { 'errmsg' => 'La version du PGDATA (%s) ne correspond pas à la version du cluster PostgreSQL ; Vous devez d\'abord mettre à niveau le PGDATA v%s vers v%s.' }, 123 | '1.11' => { 'errmsg' => 'Les sommes de contrôle ne sont pas activées dans PGDATA %s.' }, 124 | '1.12' => { 'errmsg' => 'Le sous-répertoire pg_wal ne se trouve pas sur une partition distincte de celle du PGDATA %s.' }, 125 | '1.13' => { 'errmsg' => 'Le sous-répertoire du fichier temporaire ne se trouve pas sur une partition distincte de celle de PGDATA.' }, 126 | '1.14' => { 'errmsg' => 'Impossible d\'obtenir des informations sur la partition chiffrée, la commande lsblk est manquante sur cet hôte.' }, 127 | '1.15' => { 'errmsg' => 'La vérification de la version PostgreSQL était désactivée (--no-pg-version-check) ne peut pas rechercher une mise à niveau de version mineure.' }, 128 | '1.16' => { 'errmsg' => 'La destination du tablespace %s ne devrait pas être dans le répertoire des données.' }, 129 | '1.17' => { 'errmsg' => 'La version %s de PostgreSQL est activée en tant que service systemd.' }, 130 | '2.1' => { 'errmsg' => 'L\'umask doit être 0077 ou plus restrictif pour l\'utilisateur postgres. Actuellement, il est défini sur %s.' }, 131 | '2.2' => { 'errmsg' => 'Les autorisations de PGDATA ne sont pas sécurisées : %s, doivent être drwx------.' }, 132 | '2.4' => { 'errmsg' => 'Les autorisations du fichier pg_hba.conf (%s) ne sont pas sécurisées : %s, doivent être -rw-r----- ou -rw------ -.' }, 133 | '2.5' => { 'errmsg' => 'Les autorisations de la socket Unix %s devraient être plus restrictives, par exemple: 0770 ou 0700. Actuellement elles ont positionnées à 0777.' }, 134 | '3.1' => { 'errmsg' => 'Le paramètre \'log_destination\' n\'est pas défini, la journalisation sera perdue.' }, 135 | '3.2' => { 'errmsg' => 'Le paramètre \'logging_collector\' doit être activé au lieu d\'utiliser syslog.' }, 136 | '3.3' => { 'errmsg' => 'Le paramètre \'logging_collector\' doit être activé lorsque \'log_destination\' n\'est pas défini sur syslog, la journalisation sera perdue.' }, 137 | '3.4' => { 'errmsg' => 'Le paramètre \'log_directory\' doit être défini, actuellement les écritures seront effectuées dans / et la journalisation sera perdue.' }, 138 | '3.5' => { 'errmsg' => 'Le paramètre \'log_filename\' doit être défini, la journalisation actuelle sera perdue.' }, 139 | '3.6' => { 'errmsg' => 'Le paramètre \'log_file_mode\' doit être défini sur \'0600\', la valeur actuelle est %s.' }, 140 | '3.7' => { 'errmsg' => 'Le paramètre \'log_truncate_on_rotation\' doit être activé.' }, 141 | '3.11' => { 'errmsg' => 'Le paramètre \'syslog_sequence_numbers\' doit être activé, certains messages peuvent être perdus.' }, 142 | '3.12' => { 'errmsg' => 'Le paramètre \'syslog_split_messages\' doit être activé, certains messages peuvent être tronqués.' }, 143 | '3.14' => { 'errmsg' => 'Le paramètre \'log_min_messages\' doit être défini sur \'warning\' pour éviter de tracer trop ou trop peu de messages.' }, 144 | '3.15' => { 'errmsg' => 'Le paramètre \'log_min_error_statement\' doit être défini sur \'error\' pour éviter de tracer trop ou trop peu de messages.' }, 145 | '3.16' => { 'errmsg' => 'Le paramètre \'debug_print_parse\' doit être désactivé.' }, 146 | '3.17' => { 'errmsg' => 'Le paramètre \'debug_print_rewriting\' doit être désactivé.' }, 147 | '3.18' => { 'errmsg' => 'Le paramètre \'debug_print_plan\' doit être désactivé.' }, 148 | '3.19' => { 'errmsg' => 'Le paramètre \'debug_pretty_print\' doit être activé.' }, 149 | '3.20' => { 'errmsg' => 'Le paramètre \'log_connections\' doit être activé.' }, 150 | '3.21' => { 'errmsg' => 'Le paramètre \'log_disconnections\' doit être activé.' }, 151 | '3.22' => { 'errmsg' => 'Le paramètre \'log_error_verbosity\' doit être défini sur \'verbose\'.' }, 152 | '3.23' => { 'errmsg' => 'Le paramètre \'log_hostname\' doit être désactivé.' }, 153 | '3.24' => { 'errmsg' => 'Le paramètre \'log_line_prefix\' doit contenir au moins \'%%m [%%p] : db=%%d,user=%%u,app=%%a ,client=%%h \' (pour la journalisation stderr). Pour la journalisation Syslog, le préfixe doit inclure \'user=%%u,db=%%d,app=%%a,client=%%h \'.' }, 154 | '3.25' => { 'errmsg' => 'Le paramètre \'log_statement\' doit au moins être défini sur \'ddl\'.' }, 155 | '3.26' => { 'errmsg' => 'Le paramètre \'log_timezone\' doit être défini sur \'GMT\' ou \'UTC\'.' }, 156 | '3.27' => { 'errmsg' => 'Le paramètre \'log_directory\' doit utiliser un emplacement qui ne figure pas dans PGDATA.' }, 157 | '3.28' => { 'errmsg' => 'L\'extension PostgreSQL pgAudit doit être utilisée.' }, 158 | '3.29' => { 'errmsg' => 'L\'extension PostgreSQL pgAudit n\'est pas bien configurée, le paramètre \'pgaudit.log\' doit contenir \'ddl\' et \'write\'.' }, 159 | '4.2' => { 'errmsg' => 'Il existe plusieurs superutilisateurs PostgreSQL.' }, 160 | '4.5' => { 'errmsg' => 'Certains utilisateurs de PostgreSQL ont activé Bypass RLS.' }, 161 | '4.8' => { 'errmsg' => 'Le schema public peut être utilisé par tout le monde dans la base %s.' }, 162 | '5.1' => { 'errmsg' => 'Impossible de trouver le fichier pg_hba.conf "%s".' }, 163 | '5.2' => { 'errmsg' => 'Impossible de lire le fichier pg_hba.conf "%s", raison : "%s".' }, 164 | '5.3' => { 'errmsg' => 'Impossible d\'ouvrir le répertoire "%s", raison : "%s".' }, 165 | '5.4' => { 'errmsg' => 'L\'utilisation de la méthode d\'authentification "%s" ne doit pas être utilisée. Voir la ligne %s dans le fichier %s.' }, 166 | '5.5' => { 'errmsg' => 'L\'utilisation de la méthode d\'authentification "md5" est vulnérable aux attaques par relecture de paquets. Voir la ligne %s dans le fichier %s.' }, 167 | '5.6' => { 'errmsg' => 'L\'utilisation de la méthode d\'authentification "ident" n\'est pas sécurisée, le client exécutant le serveur d\'identification doit être considéré comme non fiable. Voir la ligne %s dans le fichier %s.' }, 168 | '5.7' => { 'errmsg' => 'Utilisez %s ou n\'importe quelle méthode d\'authentification externe (gss, sspi, pam, ldap, radius ou cert) à la place.' }, 169 | '5.8' => { 'errmsg' => 'Aucune bibliothèque d\'application de difficultés de mot de passe utilisée. Pensez à utiliser l\'extension PostgreSQL credcheck ou passwordcheck.' }, 170 | '5.9' => { 'errmsg' => 'Le paramètre \'authentication_timeout\' doit être <= 60 s.' }, 171 | '5.10' => { 'errmsg' => 'Vous devez ajouter un délai d\'échec d\'authentification pour empêcher une attaque par force brute. Voir l\'extension PostgreSQL credcheck ou auth_delay.' }, 172 | '5.11' => { 'errmsg' => 'L\'utilisation du type de connexion "host" doit être rejetée lorsque "hostssl" ou "hostgssenc" est utilisé. Voir la(les) ligne(s) %s dans pg_hba.conf.' }, 173 | '5.12' => { 'errmsg' => 'L\'utilisation du cryptage SSL pour toutes les connexions à distance doit être utilisée, voir les types de connexion "hostssl" et "hostgssenc".' }, 174 | '5.13' => { 'errmsg' => 'L\'utilisation de %s \'%s\' correspond à n\'importe quelle source. Voir la ligne %s dans le fichier %s.' }, 175 | '5.14' => { 'errmsg' => 'L\'utilisation de %s \'%s\' correspond à une plage d\'Ip trop grande. Voir la ligne %s dans le fichier %s.' }, 176 | '5.15' => { 'errmsg' => 'Vous devriez être plus précis et donner la base de données et les utilisateurs autorisés à se connecter, pas "tous". Voir la ligne %s dans le fichier %s.' }, 177 | '5.16' => { 'errmsg' => 'Vous ne devez pas autoriser les superutilisateurs à se connecter à distance, uniquement à partir d\'une authentification locale et homologue. Voir la ligne %s dans le fichier %s.' }, 178 | '6.2' => { 'errmsg' => 'Le paramètre \'%s\' doit être désactivé.' }, 179 | '6.3' => { 'errmsg' => 'Le paramètre \'%s\' doit être activé.' }, 180 | '6.4' => { 'errmsg' => 'Le paramètre \'post_auth_delay\' doit être défini sur 0.' }, 181 | '6.5' => { 'errmsg' => 'L\'installation des modules FIPS n\'est pas terminée.' }, 182 | '6.6' => { 'errmsg' => 'Voir "passer le système en mode fips" pour activer le mode FIPS' }, 183 | '6.7' => { 'errmsg' => 'TLS n\'est pas activé. Le paramètre \'ssl\' doit être activé.' }, 184 | '6.8' => { 'errmsg' => 'Le paramètre \'ssl_min_protocol_version\' doit être TLS v1.3 ou plus récent.' }, 185 | '6.9' => { 'errmsg' => 'Le certificat SSL doit avoir une phrase secrète et le paramètre \'ssl_passphrase_command\' doit être défini.' }, 186 | '6.10' => { 'errmsg' => 'Pour appliquer l\'authentification TLS pour le serveur, les enregistrements "hostssl" ou "hostgssenc" appropriés doivent être ajoutés au fichier pg_hba.conf et les connexions "host" rejetées.' }, 187 | '6.11' => { 'errmsg' => 'Les extensions pgcrypto ou pgsodium ne sont pas installées.' }, 188 | '6.12' => { 'errmsg' => 'Les extensions pg_anonymize ou anon ne sont pas installées.' }, 189 | '7.1' => { 'errmsg' => 'Un utilisateur réservé à la réplication doit être créé.' }, 190 | '7.2' => { 'errmsg' => 'Le paramètre \'log_replication_commands\' doit être activé.' }, 191 | '7.4' => { 'errmsg' => 'L\'archivage WAL n\'est pas activé. Le paramètre \'archive_mode\' doit être activé.' }, 192 | '7.5' => { 'errmsg' => 'Les paramètres \'archive_command\' ou \'archive_library\' doivent être activés.' }, 193 | '7.6' => { 'errmsg' => 'Le paramètre \'primary_conninfo\' doit appliquer le cryptage TLS de la réplication (sslmode=required).' }, 194 | '7.7' => { 'errmsg' => 'Le paramètre \'primary_conninfo\' devrait activer la compression SSL (sslcompression=1).' }, 195 | '8.2' => { 'errmsg' => 'L\'outil de sauvegarde \'pgBackRest\' n\'est pas installé.' }, 196 | '8.3' => { 'errmsg' => 'Aucune strophe n\'existe pour \'pgBackRest\'.' }, 197 | }, 198 | #----------------------------------------------------------------------------- 199 | 'zh_CN' => { 200 | #----------------------------------------------------------------------------- 201 | '0.1' => { 'errmsg' => '测试通过'}, 202 | '1.1' => { 'errmsg' => '未找到 PostgreSQL 包。' }, 203 | '1.2' => { 'errmsg' => 'PostgreSQL 包不是来自 PGDG 仓库。' }, 204 | '1.3' => { 'errmsg' => '无法访问 https://www.postgresql.org/。' }, 205 | '1.4' => { 'errmsg' => '此 PostgreSQL 版本 v%s 已不再支持。' }, 206 | '1.5' => { 'errmsg' => '此 PostgreSQL 版本 v%s 不是该分支的最新版本 (%s)。' }, 207 | '1.6' => { 'errmsg' => '请查看 为什么升级。' }, 208 | '1.7' => { 'errmsg' => 'PostgreSQL 版本 %s 未作为 systemd 服务启用。' }, 209 | '1.8' => { 'errmsg' => '使用 patroni 时,PostgreSQL systemd 服务不应启用。' }, 210 | '1.9' => { 'errmsg' => '未找到正确的基础目录,PGDATA (%s) 必须先初始化(见 initdb)。' }, 211 | '1.10' => { 'errmsg' => 'PGDATA (%s) 的版本与 PostgreSQL 集群版本不匹配;需要先将 PGDATA 从 v%s 升级到 v%s。' }, 212 | '1.11' => { 'errmsg' => 'PGDATA %s 中未启用校验和。' }, 213 | '1.12' => { 'errmsg' => '子目录 pg_wal 未与 PGDATA %s 放在不同的分区。' }, 214 | '1.13' => { 'errmsg' => '临时文件的子目录未与 PGDATA 放在不同的分区。' }, 215 | '1.14' => { 'errmsg' => '无法获取有关加密分区的信息,主机上缺少命令 lsblk。' }, 216 | '1.15' => { 'errmsg' => 'PostgreSQL 版本检查已禁用 (--no-pg-version-check),无法查找小版本升级。' }, 217 | '1.16' => { 'errmsg' => '表空间位置 %s 不应在数据目录内。' }, 218 | '1.17' => { 'errmsg' => 'PostgreSQL 版本 %s 已启用为 systemd 服务。' }, 219 | '2.1' => { 'errmsg' => 'postgres 用户的 umask 必须为 0077 或更严格。当前设置为 %s。' }, 220 | '2.2' => { 'errmsg' => 'PGDATA 的权限不安全:%s,必须为 drwx------。' }, 221 | '2.4' => { 'errmsg' => 'pg_hba.conf 文件 (%s) 的权限不安全:%s,必须为 -rw-r----- 或 -rw-------。' }, 222 | '2.5' => { 'errmsg' => 'Unix 套接字 %s 的权限应该更严格,例如:0770 或 0700。当前设置为 0777。' }, 223 | '3.1' => { 'errmsg' => '未设置 \'log_destination\',日志将丢失。' }, 224 | '3.2' => { 'errmsg' => '应启用 \'logging_collector\',而不是使用 syslog。' }, 225 | '3.3' => { 'errmsg' => '当 \'log_destination\' 未设置为 syslog 时,必须启用 \'logging_collector\',日志将丢失。' }, 226 | '3.4' => { 'errmsg' => '必须设置 \'log_directory\',当前将写入 /,日志将丢失。' }, 227 | '3.5' => { 'errmsg' => '必须设置 \'log_filename\',当前日志将丢失。' }, 228 | '3.6' => { 'errmsg' => '应将 \'log_file_mode\' 设置为 \'0600\',当前值为 %s。' }, 229 | '3.7' => { 'errmsg' => '应启用 \'log_truncate_on_rotation\'。' }, 230 | '3.11' => { 'errmsg' => '应启用 \'syslog_sequence_numbers\',某些消息可能会丢失。' }, 231 | '3.12' => { 'errmsg' => '应启用 \'syslog_split_messages\',某些消息可能会被截断。' }, 232 | '3.14' => { 'errmsg' => '应将 \'log_min_messages\' 设置为 \'warning\',以避免跟踪过多或过少的消息。' }, 233 | '3.15' => { 'errmsg' => '应将 \'log_min_error_statement\' 设置为 \'error\',以避免跟踪过多或过少的消息。' }, 234 | '3.16' => { 'errmsg' => '应禁用 \'debug_print_parse\'。' }, 235 | '3.17' => { 'errmsg' => '应禁用 \'debug_print_rewritten\'。' }, 236 | '3.18' => { 'errmsg' => '应禁用 \'debug_print_plan\'。' }, 237 | '3.19' => { 'errmsg' => '应启用 \'debug_pretty_print\'。' }, 238 | '3.20' => { 'errmsg' => '应启用 \'log_connections\'。' }, 239 | '3.21' => { 'errmsg' => '应启用 \'log_disconnections\'。' }, 240 | '3.22' => { 'errmsg' => '应将 \'log_error_verbosity\' 设置为 \'verbose\'。' }, 241 | '3.23' => { 'errmsg' => '应禁用 \'log_hostname\'。' }, 242 | '3.24' => { 'errmsg' => '应确保 \'log_line_prefix\' 至少包含 \'%%m [%%p]: db=%%d,user=%%u,app=%%a,client=%%h \'(用于 stderr 日志)。对于 syslog 日志,前缀应包括 \'user=%%u,db=%%d,app=%%a,client=%%h \'。' }, 243 | '3.25' => { 'errmsg' => '应至少将 \'log_statement\' 设置为 \'ddl\'。' }, 244 | '3.26' => { 'errmsg' => '应将 \'log_timezone\' 设置为 \'GMT\' 或 \'UTC\'。' }, 245 | '3.27' => { 'errmsg' => '应使用不在 PGDATA 中的位置作为 \'log_directory\'。' }, 246 | '3.28' => { 'errmsg' => '应使用 PostgreSQL 扩展 pgAudit。' }, 247 | '3.29' => { 'errmsg' => 'PostgreSQL 扩展 pgAudit 配置不当,\'pgaudit.log\' 设置应包含 \'ddl\' 和 \'write\'。' }, 248 | '4.2' => { 'errmsg' => '存在多个 PostgreSQL 超级用户。' }, 249 | '4.5' => { 'errmsg' => '某些 PostgreSQL 用户已启用 Bypass RLS。' }, 250 | '4.8' => { 'errmsg' => '公共模式可被数据库 %s 中的任何人使用。' }, 251 | '5.1' => { 'errmsg' => '无法找到 pg_hba.conf 文件 "%s"。' }, 252 | '5.2' => { 'errmsg' => '无法读取 pg_hba.conf 文件 "%s",原因:"%s"。' }, 253 | '5.3' => { 'errmsg' => '无法打开目录 "%s",原因:"%s"。' }, 254 | '5.4' => { 'errmsg' => '不得使用 "%s" 身份验证方法。请参见文件 %s 中的第 %s 行。' }, 255 | '5.5' => { 'errmsg' => '使用 "md5" 身份验证方法易受数据包重放攻击。请参见文件 %s 中的第 %s 行。' }, 256 | '5.6' => { 'errmsg' => '使用 "ident" 身份验证方法不安全,运行 ident 服务器的客户端应被视为不可信。请参见文件 %s 中的第 %s 行。' }, 257 | '5.7' => { 'errmsg' => '请改用 %s 或任何外部身份验证方法(gss、sspi、pam、ldap、radius 或 cert)。' }, 258 | '5.8' => { 'errmsg' => '未使用密码难度强制执行库。考虑使用 credcheck 或 passwordcheck PostgreSQL 扩展。' }, 259 | '5.9' => { 'errmsg' => '应将 \'authentication_timeout\' 设置为 <= 60s。' }, 260 | '5.10' => { 'errmsg' => '应添加身份验证失败延迟以防止暴力攻击。请参见 PostgreSQL 扩展 credcheck 或 auth_delay。' }, 261 | '5.11' => { 'errmsg' => '当使用 "hostssl" 或 "hostgssenc" 时,应拒绝 "host" 连接类型。请参见 pg_hba.conf 中的第 %s 行。' }, 262 | '5.12' => { 'errmsg' => '应对所有远程连接使用 ssl 加密,请参见 "hostssl" 和 "hostgssenc" 连接类型。' }, 263 | '5.13' => { 'errmsg' => '使用 %s \'%s\' 对应于任何源。请参见文件 %s 中的第 %s 行。' }, 264 | '5.14' => { 'errmsg' => '使用 %s \'%s\' 对应于过大的 IP 范围。请参见文件 %s 中的第 %s 行。' }, 265 | '5.15' => { 'errmsg' => '应更具体,并提供允许连接的数据库和用户,而不是 "all"。请参见文件 %s 中的第 %s 行。' }, 266 | '5.16' => { 'errmsg' => '不应允许超级用户远程连接,仅允许从本地和对等身份验证。请参见文件 %s 中的第 %s 行。' }, 267 | '5.17' => { 'errmsg' => '参数 \'password_encryption\' 应设置为 \'scram-sha-256\',而不是 \'%s\'。' }, 268 | '6.2' => { 'errmsg' => '设置 \'%s\' 必须禁用。' }, 269 | '6.3' => { 'errmsg' => '设置 \'%s\' 必须启用。' }, 270 | '6.4' => { 'errmsg' => '设置 \'post_auth_delay\' 必须设置为 0。' }, 271 | '6.5' => { 'errmsg' => 'FIPS 模块的安装尚未完成。' }, 272 | '6.6' => { 'errmsg' => '请参见 "将系统切换到 fips 模式" 以启用 FIPS 模式。' }, 273 | '6.7' => { 'errmsg' => 'TLS 未启用。应激活设置 \'ssl\'。' }, 274 | '6.8' => { 'errmsg' => '设置 \'ssl_min_protocol_version\' 应为 TLS v1.3 或更新。' }, 275 | '6.9' => { 'errmsg' => 'SSL 证书应具有密码,并且应设置 \'ssl_passphrase_command\'。' }, 276 | '6.10' => { 'errmsg' => '为了强制 TLS 认证,必须在 pg_hba.conf 文件中添加适当的 "hostssl" 或 "hostgssenc" 记录,并拒绝 "host" 连接。' }, 277 | '6.11' => { 'errmsg' => '未安装扩展 pgcrypto 或 pgsodium。' }, 278 | '6.12' => { 'errmsg' => '未安装扩展 pg_anonymize 或 anon。' }, 279 | '7.1' => { 'errmsg' => '应创建一个仅用于复制的用户。' }, 280 | '7.2' => { 'errmsg' => '应启用设置 \'log_replication_commands\'。' }, 281 | '7.4' => { 'errmsg' => 'WAL 存档未激活。必须启用设置 \'archive_mode\'。' }, 282 | '7.5' => { 'errmsg' => '必须启用设置 \'archive_command\' 或 \'archive_library\'。' }, 283 | '7.6' => { 'errmsg' => '设置 \'primary_conninfo\' 必须强制复制的 TLS 加密 (sslmode=required)。' }, 284 | '7.7' => { 'errmsg' => '设置 \'primary_conninfo\' 应启用 SSL 压缩 (sslcompression=1)。' }, 285 | '8.2' => { 'errmsg' => '备份工具 \'pgBackRest\' 未安装。' }, 286 | '8.3' => { 'errmsg' => '不存在 \'pgBackRest\' 的任何节的配置。' }, 287 | }, 288 | ); 289 | 290 | 1; 291 | 292 | -------------------------------------------------------------------------------- /lib/PGDSAT/Netmask.pm: -------------------------------------------------------------------------------- 1 | #### 2 | # Formally Net::Netmask, embedded here just to avoid install of extra packages 3 | # 4 | # Copyright (C) 1998-2006 David Muir Sharnoff 5 | # Copyright (C) 2011-2013 Google, Inc. 6 | # Copyright (C) 2018-2021 Joelle Maslak 7 | #### 8 | 9 | package PGDSAT::Netmask; 10 | $PGDSAT::Netmask::VERSION = '2.0001'; 11 | use 5.006_001; 12 | 13 | # ABSTRACT: Understand and manipulate IP netmasks 14 | 15 | # Disable one-arg bless to preserve the existing interface. 16 | ## no critic (ClassHierarchies::ProhibitOneArgBless) 17 | 18 | require Exporter; 19 | @ISA = qw(Exporter); 20 | @EXPORT = qw(findNetblock findOuterNetblock findAllNetblock 21 | cidrs2contiglists range2cidrlist sort_by_ip_address 22 | dumpNetworkTable sort_network_blocks cidrs2cidrs 23 | cidrs2inverse); 24 | @EXPORT_OK = ( 25 | @EXPORT, qw(ascii2int int2quad quad2int %quadmask2bits 26 | %quadhostmask2bits imask i6mask int2ascii sameblock cmpblocks contains) 27 | ); 28 | 29 | my $remembered = {}; 30 | my %imask2bits; 31 | my %size2bits; 32 | my @imask; 33 | my @i6mask; 34 | 35 | our $SHORTNET_DEFAULT = undef; 36 | 37 | use vars qw($error $debug %quadmask2bits %quadhostmask2bits); 38 | $debug = 1; 39 | 40 | use strict; 41 | use warnings; 42 | use Carp; 43 | use Math::BigInt; 44 | use POSIX qw(floor); 45 | use overload 46 | '""' => \&desc, 47 | '<=>' => \&cmp_net_netmask_block, 48 | 'cmp' => \&cmp_net_netmask_block, 49 | 'fallback' => 1; 50 | 51 | sub new { 52 | my ( $package, $net, @params) = @_; 53 | 54 | my $mask = ''; 55 | if (@params % 2) { 56 | $mask = shift(@params); 57 | $mask = '' if !defined($mask); 58 | } 59 | my (%options) = @params; 60 | my $shortnet = ( ( exists($options{shortnet}) && $options{shortnet} ) || $SHORTNET_DEFAULT ); 61 | 62 | my $base; 63 | my $bits; 64 | my $ibase; 65 | my $proto = 'IPv4'; 66 | undef $error; 67 | 68 | if ( $net =~ m,^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/([0-9]+)$, ) { 69 | ( $base, $bits ) = ( $1, $2 ); 70 | } elsif ( $net =~ m,^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)[:/]([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$, ) { 71 | $base = $1; 72 | my $quadmask = $2; 73 | if ( exists $quadmask2bits{$quadmask} ) { 74 | $bits = $quadmask2bits{$quadmask}; 75 | } else { 76 | $error = "illegal netmask: $quadmask"; 77 | } 78 | } elsif ( $net =~ m,^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)[#]([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$, ) { 79 | $base = $1; 80 | my $hostmask = $2; 81 | if ( exists $quadhostmask2bits{$hostmask} ) { 82 | $bits = $quadhostmask2bits{$hostmask}; 83 | } else { 84 | $error = "illegal hostmask: $hostmask"; 85 | } 86 | } elsif ( ( $net =~ m,^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$, ) 87 | && ( $mask =~ m,[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$, ) ) 88 | { 89 | $base = $net; 90 | if ( exists $quadmask2bits{$mask} ) { 91 | $bits = $quadmask2bits{$mask}; 92 | } else { 93 | $error = "illegal netmask: $mask"; 94 | } 95 | } elsif ( ( $net =~ m,^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$, ) 96 | && ( $mask =~ m,0x[a-f0-9]+,i ) ) 97 | { 98 | $base = $net; 99 | my $imask = hex($mask); 100 | if ( exists $imask2bits{$imask} ) { 101 | $bits = $imask2bits{$imask}; 102 | } else { 103 | $error = "illegal netmask: $mask ($imask)"; 104 | } 105 | } elsif ( $net =~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ && !$mask ) { 106 | ( $base, $bits ) = ( $net, 32 ); 107 | } elsif ( $net =~ /^[0-9]+\.[0-9]+\.[0-9]+$/ && !$mask && $shortnet ) { 108 | ( $base, $bits ) = ( "$net.0", 24 ); 109 | } elsif ( $net =~ /^[0-9]+\.[0-9]+$/ && !$mask && $shortnet ) { 110 | ( $base, $bits ) = ( "$net.0.0", 16 ); 111 | } elsif ( $net =~ /^[0-9]+$/ && !$mask && $shortnet ) { 112 | ( $base, $bits ) = ( "$net.0.0.0", 8 ); 113 | } elsif ( $net =~ m,^([0-9]+\.[0-9]+\.[0-9]+)/([0-9]+)$, && $shortnet ) { 114 | ( $base, $bits ) = ( "$1.0", $2 ); 115 | } elsif ( $net =~ m,^([0-9]+\.[0-9]+)/([0-9]+)$, && $shortnet ) { 116 | ( $base, $bits ) = ( "$1.0.0", $2 ); 117 | } elsif ( $net =~ m,^([0-9]+)/([0-9]+)$, && $shortnet ) { 118 | ( $base, $bits ) = ( "$1.0.0.0", $2 ); 119 | } elsif ( $net eq 'default' || $net eq 'any' ) { 120 | ( $base, $bits ) = ( "0.0.0.0", 0 ); 121 | } elsif ( $net =~ m,^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\s*-\s*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$, ) 122 | { 123 | # whois format 124 | $ibase = quad2int($1); 125 | my $end = quad2int($2); 126 | $error = "illegal dotted quad: $net" 127 | unless defined($ibase) && defined($end); 128 | my $diff = ( $end || 0 ) - ( $ibase || 0 ) + 1; 129 | $bits = $size2bits{$diff}; 130 | $error = "could not find exact fit for $net" 131 | if !defined $error 132 | && ( !defined $bits 133 | || ( $ibase & ~$imask[$bits] ) ); 134 | } elsif ( $net =~ m,^([0-9a-f]*:[0-9a-f]*:[0-9a-f:]*)/([0-9]+)$, ) { 135 | # IPv6 with netmask - ex: 2001:db8::/32 136 | if ( $mask ne '' ) { $error = "mask ignored for IPv6 address" } 137 | ( $base, $bits, $proto ) = ( $1, $2, 'IPv6' ); 138 | } elsif ( $net =~ m,^([0-9a-f]*:[0-9a-f]*:[0-9a-f:]*)$, ) { 139 | # IPv6 without netmask - ex: 2001:db8::1234 140 | if ( $mask ne '' ) { $error = "mask ignored for IPv6 address" } 141 | ( $base, $bits, $proto ) = ( $1, 128, 'IPv6' ); 142 | } elsif ( $net eq 'default6' || $net eq 'any6' ) { 143 | if ( $mask ne '' ) { $error = "mask ignored for IPv6 address" } 144 | ( $base, $bits, $proto ) = ( "::", 0, 'IPv6' ); 145 | } else { 146 | $error = "could not parse $net"; 147 | $error .= " $mask" if $mask; 148 | } 149 | 150 | carp $error if $error && $debug; 151 | 152 | $bits = 0 unless $bits; 153 | if ( ( $proto eq 'IPv4' ) && ( $bits > 32 ) ) { 154 | $error = "illegal number of bits: $bits" 155 | unless $error; 156 | $bits = 32; 157 | } elsif ( ( $proto eq 'IPv6' ) && ( $bits > 128 ) ) { 158 | $error = "illegal number of bits: $bits" 159 | unless $error; 160 | $bits = 128; 161 | } 162 | 163 | $ibase = ascii2int( ( $base || '::' ), $proto ) unless (defined $ibase or $error); 164 | unless ( defined($ibase) || defined($error) ) { 165 | $error = "could not parse $net"; 166 | $error .= " $mask" if $mask; 167 | } 168 | 169 | if ($error) { 170 | $ibase = 0; 171 | $bits = 0; 172 | } 173 | 174 | $ibase = i_getnet_addr( $ibase, $bits, $proto ); 175 | 176 | return bless { 177 | 'IBASE' => $ibase, 178 | 'BITS' => $bits, 179 | 'PROTOCOL' => $proto, 180 | ( $error ? ( 'ERROR' => $error ) : () ), 181 | }; 182 | } 183 | 184 | sub i_getnet_addr { 185 | my ( $ibase, $bits, $proto ) = @_; 186 | 187 | if ( !defined($ibase) ) { return; } 188 | 189 | if ( $proto eq 'IPv4' ) { 190 | return $ibase & $imask[$bits]; 191 | } else { 192 | return $ibase & $i6mask[$bits]; 193 | } 194 | } 195 | 196 | sub new2 { 197 | goto &safe_new; 198 | } 199 | 200 | sub safe_new { 201 | local ($debug) = 0; 202 | my $net = new(@_); 203 | return if $error; 204 | return $net; 205 | } 206 | 207 | sub errstr { return $error; } 208 | sub debug { my $this = shift; return ( @_ ? $debug = shift : $debug ) } 209 | 210 | sub base { my ($this) = @_; return int2ascii( $this->{IBASE}, $this->{PROTOCOL} ); } 211 | sub bits { my ($this) = @_; return $this->{'BITS'}; } 212 | sub protocol { my ($this) = @_; return $this->{'PROTOCOL'}; } 213 | 214 | sub size { 215 | my ($this) = @_; 216 | 217 | if ( $this->{PROTOCOL} eq 'IPv4' ) { 218 | return 2**( 32 - $this->{'BITS'} ); 219 | } else { 220 | return Math::BigInt->new(2)->bpow( 128 - $this->{'BITS'} ); 221 | } 222 | } 223 | 224 | sub next { ## no critic: (Subroutines::ProhibitBuiltinHomonyms) 225 | my ($this) = @_; 226 | # TODO: CONSOLIDATE 227 | if ( $this->{PROTOCOL} eq 'IPv4' ) { 228 | return int2quad( $this->{'IBASE'} + $this->size() ); 229 | } else { 230 | return $this->_ipv6next( $this->size ); 231 | } 232 | } 233 | 234 | sub broadcast { 235 | my ($this) = @_; 236 | 237 | return int2ascii( $this->{'IBASE'} + $this->size() - 1, $this->{PROTOCOL} ); 238 | } 239 | 240 | *first = \&base; 241 | *last = \&broadcast; 242 | 243 | sub desc { 244 | return int2ascii( $_[0]->{IBASE}, $_[0]->{PROTOCOL} ) . '/' . $_[0]->{BITS}; 245 | } 246 | 247 | sub imask { 248 | return ( 2**32 - ( 2**( 32 - $_[0] ) ) ); 249 | } 250 | 251 | sub i6mask { 252 | my $bits = shift; 253 | return Math::BigInt->new(2)->bpow(128) - Math::BigInt->new(2)->bpow( 128 - $bits ); 254 | } 255 | 256 | sub mask { 257 | my ($this) = @_; 258 | 259 | if ( $this->{PROTOCOL} eq 'IPv4' ) { 260 | return int2quad( $imask[ $this->{'BITS'} ] ); 261 | } else { 262 | return int2ascii( $i6mask[ $this->{'BITS'} ], $this->{PROTOCOL} ); 263 | } 264 | } 265 | 266 | sub hostmask { 267 | my ($this) = @_; 268 | 269 | if ( $this->{PROTOCOL} eq 'IPv4' ) { 270 | return int2quad( ~$imask[ $this->{BITS} ] ); 271 | } else { 272 | return int2ascii( $i6mask[ $this->{BITS} ] ^ $i6mask[128], $this->{PROTOCOL} ); 273 | } 274 | } 275 | 276 | sub nth { 277 | my ( $this, $index, $bitstep ) = @_; 278 | 279 | my $maxbits = $this->{PROTOCOL} eq 'IPv4' ? 32 : 128; 280 | 281 | my $size = $this->size(); 282 | my $ibase = $this->{'IBASE'}; 283 | $bitstep = $maxbits unless $bitstep; 284 | my $increment = 2**( $maxbits - $bitstep ); 285 | $index *= $increment; 286 | $index += $size if $index < 0; 287 | return if $index < 0; 288 | return if $index >= $size; 289 | 290 | my $i = $ibase + $index; 291 | return int2ascii( $i, $this->{PROTOCOL} ); 292 | } 293 | 294 | sub enumerate { 295 | my ( $this, $bitstep ) = @_; 296 | my $proto = $this->{PROTOCOL}; 297 | 298 | # Set default step size by protocol 299 | $bitstep = ( $proto eq 'IPv4' ? 32 : 128 ) unless $bitstep; 300 | 301 | my $size = $this->size(); 302 | 303 | my @ary; 304 | ### We should be able to consolidate this 305 | if ( $proto eq 'IPv4' ) { 306 | my $increment = 2**( 32 - $bitstep ); 307 | my $ibase = $this->{'IBASE'}; 308 | for ( my $i = 0; $i < $size; $i += $increment ) { 309 | push( @ary, int2quad( $ibase + $i ) ); 310 | } 311 | } else { 312 | my $increment = Math::BigInt->new(2)->bpow( 128 - $bitstep ); 313 | 314 | if ( ( $size / $increment ) > 1_000_000_000 ) { 315 | # Let's help the user out and catch really obvious issues. 316 | # Asking for a billion IP addresses is probably one of them. 317 | # 318 | # That said, please contact the author if this number causes 319 | # you issues! 320 | confess("More than 1,000,000,000 results would be returned, dieing"); 321 | } 322 | 323 | for ( my $i = Math::BigInt->new(0); $i < $size; $i += $increment ) { 324 | push( @ary, $this->_ipv6next($i) ); 325 | } 326 | } 327 | return @ary; 328 | } 329 | 330 | sub _ipv6next { 331 | my ( $this, $bitstep ) = @_; 332 | 333 | my $istart = $this->{IBASE}; 334 | my $val = $istart + $bitstep; 335 | 336 | return ipv6Cannonical( int2ascii( $val, $this->{PROTOCOL} ) ); 337 | } 338 | 339 | sub inaddr { 340 | my ($this) = @_; 341 | 342 | if ( $this->{PROTOCOL} eq 'IPv4' ) { 343 | return $this->inaddr4(); 344 | } else { 345 | return $this->inaddr6(); 346 | } 347 | } 348 | 349 | sub inaddr4 { 350 | my ($this) = @_; 351 | my $ibase = $this->{'IBASE'}; 352 | my $blocks = floor( $this->size() / 256 ); 353 | return ( 354 | join( '.', unpack( 'xC3', pack( 'V', $ibase ) ) ) . ".in-addr.arpa", 355 | $ibase % 256, 356 | $ibase % 256 + $this->size() - 1 357 | ) if $blocks == 0; 358 | my @ary; 359 | for ( my $i = 0; $i < $blocks; $i++ ) { 360 | push( @ary, 361 | join( '.', unpack( 'xC3', pack( 'V', $ibase + $i * 256 ) ) ) . ".in-addr.arpa", 362 | 0, 255 ); 363 | } 364 | return @ary; 365 | } 366 | 367 | sub inaddr6 { 368 | my ($this) = @_; 369 | 370 | my (@digits) = split //, $this->{IBASE}->to_hex; 371 | 372 | my $static = floor( $this->{BITS} / 4 ); 373 | my $len = floor( ( $static + 3 ) / 4 ); 374 | my $remainder = $this->{BITS} % 4; 375 | my $blocks = $remainder ? ( 2**( 4 - $remainder ) ) : 1; 376 | 377 | my @tail; 378 | if ( !$len ) { 379 | # Specal case: 0 len 380 | return ('ip6.arpa'); 381 | } 382 | push @tail, reverse( @digits[ 0 .. ( $static - 1 ) ] ), 'ip6.arpa'; 383 | 384 | if ( !$remainder ) { 385 | # Special case - at nibble boundary already 386 | return ( join '.', @tail ); 387 | } 388 | 389 | my $last = hex $digits[$static]; 390 | my @ary; 391 | for ( my $i = 0; $i < $blocks; $i++ ) { 392 | push @ary, join( '.', sprintf( "%x", $last ), @tail ); 393 | $last++; 394 | } 395 | 396 | return @ary; 397 | } 398 | 399 | sub tag { 400 | my $this = shift; 401 | my $tag = shift; 402 | my $val = $this->{ 'T' . $tag }; 403 | $this->{ 'T' . $tag } = $_[0] if @_; 404 | return $val; 405 | } 406 | 407 | sub quad2int { 408 | my @bytes = split( /\./, $_[0] ); 409 | 410 | return unless @bytes == 4; 411 | return unless !grep { !( /^(([0-9])|([1-9][0-9]*))$/ && $_ < 256 ) } @bytes; 412 | 413 | return unpack( "N", pack( "C4", @bytes ) ); 414 | } 415 | 416 | sub int2quad { 417 | return join( '.', unpack( 'C4', pack( "N", $_[0] ) ) ); 418 | } 419 | 420 | # Uses the internal "raw" representation (such as IBASE). 421 | # For IPv4, this is an integer 422 | # For IPv6, this is a raw bit string. 423 | sub int2ascii { 424 | if ( $_[1] eq 'IPv4' ) { 425 | return join( '.', unpack( 'C4', pack( "N", $_[0] ) ) ); 426 | } elsif ( $_[1] eq 'IPv6' ) { 427 | my $addr = ( ref $_[0] ) ne '' ? $_[0]->to_hex : Math::BigInt->new( $_[0] )->to_hex; 428 | return ipv6Cannonical($addr); 429 | } else { 430 | confess("Incorrect call"); 431 | } 432 | } 433 | 434 | # Produces the internal "raw" representation (such as IBASE). 435 | # For IPv4, this is an integer 436 | # For IPv6, this is a raw bit string. 437 | sub ascii2int { 438 | if ( $_[1] eq 'IPv4' ) { 439 | return quad2int( $_[0] ); 440 | } elsif ( $_[1] eq 'IPv6' ) { 441 | return ipv6ascii2int( $_[0] ); 442 | } else { 443 | confess("Incorrect call"); 444 | } 445 | } 446 | 447 | # Take an IPv6 ASCII address and produce a raw value 448 | sub ipv6ascii2int { 449 | my $addr = shift; 450 | 451 | $addr = ipv6NonCompacted($addr); 452 | $addr = join '', map { sprintf( "%04x", hex($_) ) } split( /:/, $addr ); 453 | 454 | return Math::BigInt->from_hex($addr); 455 | } 456 | 457 | # Takes an IPv6 address and produces a standard version seperated by 458 | # colons (without compacting) 459 | sub ipv6NonCompacted { 460 | my $addr = shift; 461 | 462 | if ( $addr !~ /:/ ) { 463 | if ( length($addr) < 32 ) { 464 | $addr = ( "0" x ( 32 - length($addr) ) ) . $addr; 465 | } 466 | $addr =~ s/(....)(?=....)/$1:/gsx; 467 | } 468 | 469 | # Handle address format with trailing IPv6 470 | # Ex: 0:0:0:0:1.2.3.4 471 | if ( $addr =~ m/^[0-9a-f:]+[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/i ) { 472 | my ( $l, $r1, $r2, $r3, $r4 ) = 473 | $addr =~ m/^([0-9a-f:]+)([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/i; 474 | $addr = sprintf( "%s%02x%02x:%02x%02x", $l, $r1, $r2, $r3, $r4 ); 475 | } 476 | 477 | my ( $left, $right ) = split /::/, $addr; 478 | if ( !defined($right) ) { $right = '' } 479 | my (@lparts) = split /:/, $left; 480 | my (@rparts) = split /:/, $right; 481 | 482 | # Strip leading 0's & lowercase 483 | @lparts = map { $_ =~ s/^0+([0-9a-f]+)/$1/; lc($_) } @lparts; 484 | @rparts = map { $_ =~ s/^0+([0-9a-f]+)/$1/; lc($_) } @rparts; 485 | 486 | # Expand :: 487 | my $missing = 8 - ( @lparts + @rparts ); 488 | if ($missing) { 489 | $addr = join ':', @lparts, ( 0, 0, 0, 0, 0, 0, 0, 0 )[ 0 .. $missing - 1 ], @rparts; 490 | } else { 491 | $addr = join ':', @lparts, @rparts; 492 | } 493 | 494 | return $addr; 495 | } 496 | 497 | # Compacts an IPv6 address (reduces successive :0: runs) 498 | sub ipv6AsciiCompact { 499 | my $addr = shift; 500 | 501 | # Compress, per RFC5952 502 | if ( $addr =~ s/^0:0:0:0:0:0:0:0$/::/ ) { 503 | return $addr; 504 | } elsif ( $addr =~ s/(:?^|:)0:0:0:0:0:0:0(:?:|$)/::/ ) { 505 | return $addr; 506 | } elsif ( $addr =~ s/(:?^|:)0:0:0:0:0:0(:?:|$)/::/ ) { 507 | return $addr; 508 | } elsif ( $addr =~ s/(:?^|:)0:0:0:0:0(:?:|$)/::/ ) { 509 | return $addr; 510 | } elsif ( $addr =~ s/(:?^|:)0:0:0:0(:?:|$)/::/ ) { 511 | return $addr; 512 | } elsif ( $addr =~ s/(:?^|:)0:0:0(:?:|$)/::/ ) { 513 | return $addr; 514 | } elsif ( $addr =~ s/(:?^|:)0:0(:?:|$)/::/ ) { 515 | return $addr; 516 | } elsif ( $addr =~ s/(:?^|:)0(:?:|$)/::/ ) { 517 | return $addr; 518 | } 519 | return $addr; 520 | } 521 | # Cannonicalize IPv6 addresses in ascii format 522 | sub ipv6Cannonical { 523 | my $addr = shift; 524 | 525 | $addr = ipv6NonCompacted($addr); 526 | $addr = ipv6AsciiCompact($addr); 527 | 528 | return $addr; 529 | } 530 | 531 | # IPv6 addresses are stored with a leading zero. 532 | sub storeNetblock { 533 | my ( $this, $t ) = @_; 534 | $t = $remembered unless $t; 535 | 536 | my $base = $this->{'IBASE'}; 537 | if ( $this->{PROTOCOL} eq 'IPv6' ) { 538 | $base = "0$base"; 539 | } 540 | 541 | $t->{$base} = [] unless exists $t->{$base}; 542 | 543 | my $mb = maxblock($this); 544 | my $bits = $this->{'BITS'}; 545 | my $i = $bits - $mb; 546 | 547 | return ( $t->{$base}[$i] = $this ); 548 | } 549 | 550 | sub deleteNetblock { 551 | my ( $this, $t ) = @_; 552 | $t = $remembered unless $t; 553 | 554 | my $base = $this->{'IBASE'}; 555 | if ( $this->{PROTOCOL} eq 'IPv6' ) { 556 | $base = "0$base"; 557 | } 558 | 559 | my $mb = maxblock($this); 560 | my $bits = $this->{'BITS'}; 561 | my $i = $bits - $mb; 562 | 563 | return unless defined $t->{$base}; 564 | 565 | undef $t->{$base}->[$i]; 566 | 567 | for my $x ( @{ $t->{$base} } ) { 568 | return if $x; 569 | } 570 | return delete $t->{$base}; 571 | } 572 | 573 | sub findNetblock { 574 | my ( $ascii, $t ) = @_; 575 | $t = $remembered unless $t; 576 | 577 | my $proto = ( $ascii =~ m/:/ ) ? 'IPv6' : 'IPv4'; 578 | 579 | my $ip = ascii2int( $ascii, $proto ); 580 | return unless defined $ip; 581 | my %done; 582 | 583 | my $maxbits = $proto eq 'IPv6' ? 128 : 32; 584 | for ( my $bits = $maxbits; $bits >= 0; $bits-- ) { 585 | my $nb = i_getnet_addr( $ip, $bits, $proto ); 586 | if ( $proto eq 'IPv6' ) { 587 | $nb = "0$nb"; 588 | } 589 | next unless exists $t->{$nb}; 590 | my $mb = imaxblock( $nb, $maxbits, $proto ); 591 | next if $done{$mb}++; 592 | my $i = $bits - $mb; 593 | while ( $i >= 0 ) { 594 | return $t->{$nb}->[$i] 595 | if defined $t->{$nb}->[$i]; 596 | $i--; 597 | } 598 | } 599 | return; 600 | } 601 | 602 | sub findOuterNetblock { 603 | my ( $ipstr, $t ) = @_; 604 | $t = $remembered unless $t; 605 | 606 | my $proto; 607 | my $maxbits; 608 | 609 | my $ip; 610 | my $len; 611 | if ( ref($ipstr) ) { 612 | $proto = $ipstr->{PROTOCOL}; 613 | $maxbits = $proto eq 'IPv4' ? 32 : 128; 614 | $ip = $ipstr->{IBASE}; 615 | $len = $ipstr->{BITS}; 616 | } else { 617 | $proto = ( $ipstr =~ m/:/ ) ? 'IPv6' : 'IPv4'; 618 | $maxbits = $proto eq 'IPv4' ? 32 : 128; 619 | $ip = ascii2int( $ipstr, $proto ); 620 | $len = $maxbits; 621 | } 622 | 623 | for ( my $bits = 0; $bits <= $len; $bits++ ) { 624 | my $nb = $ip & ( $proto eq 'IPv4' ? $imask[$bits] : $i6mask[$bits] ); 625 | if ( $proto eq 'IPv6' ) { 626 | $nb = "0$nb"; 627 | } 628 | next unless exists $t->{$nb}; 629 | my $mb = imaxblock( $nb, $len, $proto ); 630 | my $i = $bits - $mb; 631 | confess "$mb, $bits, $ipstr, $nb" if $i < 0; 632 | confess "$mb, $bits, $ipstr, $nb" if $i > $maxbits; 633 | while ( $i >= 0 ) { 634 | return $t->{$nb}->[$i] 635 | if defined $t->{$nb}->[$i]; 636 | $i--; 637 | } 638 | } 639 | return; 640 | } 641 | 642 | sub findAllNetblock { 643 | my ( $ipstr, $t ) = @_; 644 | $t = $remembered unless $t; 645 | 646 | my $proto = ( $ipstr =~ m/:/ ) ? 'IPv6' : 'IPv4'; 647 | my $maxbits = $proto eq 'IPv4' ? 32 : 128; 648 | 649 | my $ip = ascii2int( $ipstr, $proto ); 650 | 651 | my %done; 652 | my @ary; 653 | for ( my $bits = $maxbits; $bits >= 0; $bits-- ) { 654 | my $nb = $ip & ( $proto eq 'IPv4' ? $imask[$bits] : $i6mask[$bits] ); 655 | if ( $proto eq 'IPv6' ) { 656 | $nb = "0$nb"; 657 | } 658 | next unless exists $t->{$nb}; 659 | my $mb = imaxblock( $nb, $maxbits, $proto ); 660 | next if $done{$mb}++; 661 | my $i = $bits - $mb; 662 | confess "$mb, $bits, $ipstr, $nb" if $i < 0; 663 | confess "$mb, $bits, $ipstr, $nb" if $i > $maxbits; 664 | while ( $i >= 0 ) { 665 | push( @ary, $t->{$nb}->[$i] ) 666 | if defined $t->{$nb}->[$i]; 667 | $i--; 668 | } 669 | } 670 | return @ary; 671 | } 672 | 673 | sub dumpNetworkTable { 674 | my ($t) = @_; 675 | $t = $remembered unless $t; 676 | 677 | my @ary; 678 | foreach my $base ( keys %$t ) { 679 | push @ary, grep { defined($_) } @{ $t->{base} }; 680 | for my $x ( @{ $t->{$base} } ) { 681 | push( @ary, $x ) 682 | if defined $x; 683 | } 684 | } 685 | 686 | return ( sort @ary ); 687 | } 688 | 689 | sub checkNetblock { 690 | my ( $this, $t ) = @_; 691 | $t = $remembered unless $t; 692 | 693 | my $base = $this->{'IBASE'}; 694 | 695 | my $mb = maxblock($this); 696 | my $bits = $this->{'BITS'}; 697 | my $i = $bits - $mb; 698 | 699 | return defined $t->{$base}->[$i]; 700 | } 701 | 702 | sub match { 703 | my ( $this, $ip ) = @_; 704 | my $proto = $this->{PROTOCOL}; 705 | 706 | # Two different protocols: return undef 707 | if ( $ip =~ /:/ ) { 708 | if ( $proto ne 'IPv6' ) { return } 709 | } else { 710 | if ( $proto ne 'IPv4' ) { return } 711 | } 712 | 713 | my $i = ascii2int( $ip, $this->{PROTOCOL} ); 714 | my $ia = i_getnet_addr( $i, $this->{BITS}, $proto ); 715 | 716 | if ( $proto eq 'IPv4' ) { 717 | if ( $ia == $this->{IBASE} ) { 718 | return ( ( $i & ~( $this->{IBASE} ) ) || "0 " ); 719 | } else { 720 | return 0; 721 | } 722 | } else { 723 | if ( $ia == $this->{IBASE} ) { 724 | return ( ( $i - $this->{IBASE} ) || "0 " ); 725 | } else { 726 | return 0; 727 | } 728 | } 729 | } 730 | 731 | sub maxblock { 732 | my ($this) = @_; 733 | return ( !defined $this->{ERROR} ) 734 | ? imaxblock( $this->{IBASE}, $this->{BITS}, $this->{PROTOCOL} ) 735 | : undef; 736 | } 737 | 738 | sub nextblock { 739 | my ( $this, $index ) = @_; 740 | $index = 1 unless defined $index; 741 | my $ibase = $this->{IBASE}; 742 | if ( $this->{PROTOCOL} eq 'IPv4' ) { 743 | $ibase += $index * 2**( 32 - $this->{BITS} ); 744 | } else { 745 | $ibase += $index * Math::BigInt->new(2)->bpow( 128 - $this->{BITS} ); 746 | } 747 | 748 | my $newblock = bless { 749 | IBASE => $ibase, 750 | BITS => $this->{BITS}, 751 | PROTOCOL => $this->{PROTOCOL}, 752 | }; 753 | 754 | if ( $this->{PROTOCOL} eq 'IPv4' ) { 755 | return if $newblock->{IBASE} >= 2**32; 756 | } else { 757 | return if $newblock->{IBASE} >= Math::BigInt->new(2)->bpow(128); 758 | } 759 | 760 | return if $newblock->{IBASE} < 0; 761 | return $newblock; 762 | } 763 | 764 | sub imaxblock { 765 | my ( $ibase, $tbit, $proto ) = @_; 766 | confess unless defined $ibase; 767 | 768 | if ( !defined($proto) ) { $proto = 'IPv4'; } 769 | 770 | while ( $tbit > 0 ) { 771 | my $ia = i_getnet_addr( $ibase, $tbit - 1, $proto ); 772 | last if ( $ia != $ibase ); 773 | $tbit--; 774 | } 775 | return $tbit; 776 | } 777 | 778 | sub range2cidrlist { 779 | my ( $startip, $endip ) = @_; 780 | 781 | my $proto; 782 | if ( $startip =~ m/:/ ) { 783 | if ( $endip =~ m/:/ ) { $proto = 'IPv6'; } 784 | } else { 785 | if ( $endip !~ m/:/ ) { $proto = 'IPv4'; } 786 | } 787 | if ( !defined($proto) ) { confess("Cannot mix IPv4 and IPv6 in range2cidrlist()"); } 788 | 789 | my $start = ascii2int( $startip, $proto ); 790 | my $end = ascii2int( $endip, $proto ); 791 | 792 | ( $start, $end ) = ( $end, $start ) 793 | if $start > $end; 794 | return irange2cidrlist( $start, $end, $proto ); 795 | } 796 | 797 | sub irange2cidrlist { 798 | my ( $start, $end, $proto ) = @_; 799 | if ( !defined($proto) ) { $proto = 'IPv4' } 800 | 801 | my $bits = $proto eq 'IPv4' ? 32 : 128; 802 | 803 | my @result; 804 | while ( $end >= $start ) { 805 | my $maxsize = imaxblock( $start, $bits, $proto ); 806 | my $maxdiff; 807 | if ( $proto eq 'IPv4' ) { 808 | $maxdiff = $bits - _log2( $end - $start + 1 ); 809 | } else { 810 | $maxdiff = $bits - ( $end - $start + 1 )->blog(2); 811 | } 812 | $maxsize = $maxdiff if $maxsize < $maxdiff; 813 | push( 814 | @result, 815 | bless { 816 | 'IBASE' => $start, 817 | 'BITS' => $maxsize, 818 | 'PROTOCOL' => $proto, 819 | } 820 | ); 821 | if ( $proto eq 'IPv4' ) { 822 | $start += 2**( 32 - $maxsize ); 823 | } else { 824 | $start += Math::BigInt->new(2)->bpow( $bits - $maxsize ); 825 | } 826 | } 827 | return @result; 828 | } 829 | 830 | sub cidrs2contiglists { 831 | my (@cidrs) = sort_network_blocks(@_); 832 | my @result; 833 | while (@cidrs) { 834 | my (@r) = shift(@cidrs); 835 | my $max = $r[0]->{IBASE} + $r[0]->size; 836 | while ( $cidrs[0] && $cidrs[0]->{IBASE} <= $max ) { 837 | my $nm = $cidrs[0]->{IBASE} + $cidrs[0]->size; 838 | $max = $nm if $nm > $max; 839 | push( @r, shift(@cidrs) ); 840 | } 841 | push( @result, [@r] ); 842 | } 843 | return @result; 844 | } 845 | 846 | sub cidrs2cidrs { 847 | my (@cidrs) = sort_network_blocks(@_); 848 | my @result; 849 | 850 | my $proto; 851 | if ( scalar(@cidrs) ) { 852 | $proto = $cidrs[0]->{PROTOCOL}; 853 | if ( grep { $proto ne $_->{PROTOCOL} } @cidrs ) { 854 | confess("Cannot call cidrs2cidrs with mixed protocol arguments"); 855 | } 856 | } 857 | 858 | while (@cidrs) { 859 | my (@r) = shift(@cidrs); 860 | 861 | my $max = $r[0]->{IBASE} + $r[0]->size; 862 | while ( $cidrs[0] && $cidrs[0]->{IBASE} <= $max ) { 863 | my $nm = $cidrs[0]->{IBASE} + $cidrs[0]->size; 864 | $max = $nm if $nm > $max; 865 | push( @r, shift(@cidrs) ); 866 | } 867 | my $start = $r[0]->{IBASE}; 868 | my $end = $max - 1; 869 | push( @result, irange2cidrlist( $start, $end, $proto ) ); 870 | } 871 | return @result; 872 | } 873 | 874 | sub cidrs2inverse { 875 | my $outer = shift; 876 | $outer = __PACKAGE__->new2($outer) || croak($error) unless ref($outer); 877 | 878 | # cidrs2cidrs validates that everything is in the same address 879 | # family 880 | my (@cidrs) = cidrs2cidrs(@_); 881 | my $proto; 882 | if ( scalar(@cidrs) ) { 883 | $proto = $cidrs[0]->{PROTOCOL}; 884 | } 885 | 886 | my $first = $outer->{IBASE}; 887 | my $last = $first + $outer->size() - 1; 888 | shift(@cidrs) while $cidrs[0] && $cidrs[0]->{IBASE} + $cidrs[0]->size < $first; 889 | my @r; 890 | while ( @cidrs && $first <= $last ) { 891 | 892 | if ( $first < $cidrs[0]->{IBASE} ) { 893 | if ( $last <= $cidrs[0]->{IBASE} - 1 ) { 894 | return ( @r, irange2cidrlist( $first, $last, $proto ) ); 895 | } 896 | push( @r, irange2cidrlist( $first, $cidrs[0]->{IBASE} - 1, $proto ) ); 897 | } 898 | last if $cidrs[0]->{IBASE} > $last; 899 | $first = $cidrs[0]->{IBASE} + $cidrs[0]->size; 900 | shift(@cidrs); 901 | } 902 | if ( $first <= $last ) { 903 | push( @r, irange2cidrlist( $first, $last, $proto ) ); 904 | } 905 | return @r; 906 | } 907 | 908 | sub by_net_netmask_block { 909 | return $a->{'IBASE'} <=> $b->{'IBASE'} 910 | || $a->{'BITS'} <=> $b->{'BITS'}; 911 | } 912 | 913 | sub sameblock { 914 | return !cmpblocks(@_); 915 | } 916 | 917 | sub cmpblocks { 918 | my $this = shift; 919 | my $class = ref $this; 920 | my $other = ( ref $_[0] ) ? shift : $class->new(@_); 921 | return cmp_net_netmask_block( $this, $other ); 922 | } 923 | 924 | sub contains { 925 | my $this = shift; 926 | my $class = ref $this; 927 | my $other = ( ref $_[0] ) ? shift : $class->new(@_); 928 | return 0 if $this->{IBASE} > $other->{IBASE}; 929 | return 0 if $this->{BITS} > $other->{BITS}; 930 | return 0 if $other->{IBASE} > $this->{IBASE} + $this->size - 1; 931 | return 1; 932 | } 933 | 934 | sub cmp_net_netmask_block { 935 | if ( ( $_[0]->{PROTOCOL} eq 'IPv4' ) && ( $_[1]->{PROTOCOL} eq 'IPv4' ) ) { 936 | # IPv4 937 | return ( $_[0]->{IBASE} <=> $_[1]->{IBASE} || $_[0]->{BITS} <=> $_[1]->{BITS} ); 938 | } elsif ( ( $_[0]->{PROTOCOL} eq 'IPv6' ) && ( $_[1]->{PROTOCOL} eq 'IPv6' ) ) { 939 | # IPv6 940 | return ( $_[0]->{IBASE} <=> $_[1]->{IBASE} || $_[0]->{BITS} <=> $_[1]->{BITS} ); 941 | } else { 942 | # IPv4 to IPv6, order by protocol 943 | return ( $_[0]->{PROTOCOL} cmp $_[1]->{PROTOCOL} ); 944 | } 945 | } 946 | 947 | sub sort_network_blocks { 948 | return map { $_->[0] } 949 | sort { $a->[3] cmp $b->[3] || $a->[1] <=> $b->[1] || $a->[2] <=> $b->[2] } 950 | map { [ $_, $_->{IBASE}, $_->{BITS}, $_->{PROTOCOL} ] } @_; 951 | } 952 | 953 | sub sort_by_ip_address { 954 | return map { $_->[0] } 955 | sort { $a->[1] cmp $b->[1] } 956 | map { [ $_, pack( "C4", split( /\./, $_ ) ) ] } @_; 957 | 958 | } 959 | 960 | sub split ## no critic: (Subroutines::ProhibitBuiltinHomonyms) 961 | { 962 | my ( $self, $parts ) = @_; 963 | 964 | my $num_ips = $self->size; 965 | 966 | confess "Parts must be defined and greater than 0." 967 | unless defined($parts) && $parts > 0; 968 | 969 | confess "Netmask only contains $num_ips IPs. Cannot split into $parts." 970 | unless $num_ips >= $parts; 971 | 972 | my $log2 = _log2($parts); 973 | 974 | confess "Parts count must be a number of base 2. Got: $parts" 975 | unless ( 2**$log2 ) == $parts; 976 | 977 | my $new_mask = $self->bits + $log2; 978 | 979 | return map { PGDSAT::Netmask->new( $_ . "/" . $new_mask ) } 980 | map { $self->nth( ( $num_ips / $parts ) * $_ ) } ( 0 .. ( $parts - 1 ) ); 981 | } 982 | 983 | # Implement log2 sub routine directly, to avoid precision problems with floor() 984 | # problems with perls built with uselongdouble defined. 985 | # Credit: xenu, on IRC 986 | sub _log2 { 987 | my $n = shift; 988 | 989 | my $ret = 0; 990 | $ret++ while ( $n >>= 1 ); 991 | 992 | return $ret; 993 | } 994 | 995 | BEGIN { 996 | for ( my $i = 0; $i <= 32; $i++ ) { 997 | $imask[$i] = imask($i); 998 | $imask2bits{ $imask[$i] } = $i; 999 | $quadmask2bits{ int2quad( $imask[$i] ) } = $i; 1000 | $quadhostmask2bits{ int2quad( ~$imask[$i] ) } = $i; 1001 | $size2bits{ 2**( 32 - $i ) } = $i; 1002 | } 1003 | 1004 | for ( my $i = 0; $i <= 128; $i++ ) { 1005 | $i6mask[$i] = i6mask($i); 1006 | } 1007 | } 1008 | 1; 1009 | -------------------------------------------------------------------------------- /lib/PGDSAT/Report.pm: -------------------------------------------------------------------------------- 1 | package PGDSAT::Report; 2 | 3 | #------------------------------------------------------------------------------ 4 | # Project : PostgreSQL Database Security Assement Tool 5 | # Name : PGDSAT/Report.pm 6 | # Language : Perl 7 | # Authors : Gilles Darold 8 | # Copyright: Copyright (c) 2024 HexaCluster Corp 9 | # Function : Module containing methods to build the report 10 | #------------------------------------------------------------------------------ 11 | use vars qw($VERSION); 12 | use strict; 13 | 14 | $VERSION = '1.1'; 15 | 16 | #### 17 | # Build the report 18 | #### 19 | sub generate_report 20 | { 21 | my $self = shift; 22 | 23 | # Print the HTML header 24 | if ($self->{format} eq 'html') { 25 | begin_html($self); 26 | } else { 27 | $self->{content} = "POSTGRESQL SECURITY ASSESSEMENT REPORT \U$self->{title}\E\n\n"; 28 | } 29 | 30 | 31 | # Add audit summary results 32 | if ($self->{format} eq 'text') { 33 | resume_as_text($self); 34 | } else { 35 | resume_as_html($self); 36 | } 37 | 38 | # Add the detailled result of the checks 39 | if ($self->{format} eq 'text') 40 | { 41 | $self->{content} .= "\n\n"; 42 | $self->{content} .= "#"x80 . "\n"; 43 | $self->{content} .= "# Detailled security assessment\n"; 44 | $self->{content} .= "#"x80 . "\n\n"; 45 | } 46 | else 47 | { 48 | $self->{content} .= "
\n"; 49 | $self->{content} .= "

Detailled security assessment

\n"; 50 | } 51 | 52 | $self->{content} .= $self->{details}; 53 | 54 | if ($self->{format} eq 'html') { 55 | end_html($self); 56 | } else { 57 | $self->{content} .= "\n(*) Check not part of the CIS Benchmark\n"; 58 | } 59 | } 60 | 61 | #### 62 | # print the HTML header 63 | #### 64 | sub begin_html 65 | { 66 | my $self = shift; 67 | 68 | my $date = localtime(time); 69 | 70 | # Embedded logo 71 | my $pgdsat_logo = 'data:image/png;base64, 72 | iVBORw0KGgoAAAANSUhEUgAAAEgAAABbCAYAAADQr8NyAAAAx3pUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjabVBbDsMwCPvPKXaEBMiD46RrJ+0GO/6cQKu2qqWAg5FDCNvv+wmvAUoSJNdWtJQIiIpSB2nR0GdMUWackF1L13rIiwuEEiOzXVvx/r2eDgNLHSyfjNrbheUqqLh/uxn5QzwmIpDVjdSNmExIbtDtW7Foq+cvLFu8otkJI3Cd3ofJ/S4V21sziky0ceKIyFxsAB5HAncQRiTGUGgS8IzcwdQnwUKe9rQj/AE4HVla5u9k5QAAAYRpQ0NQSUNDIHByb2ZpbGUAAHicfZE9SMNAHMVfU6UiFQcLijhkqE4W1Io4ahWKUCHUCq06mFz6ITRpSFJcHAXXgoMfi1UHF2ddHVwFQfADxNnBSdFFSvxfUmgR48FxP97de9y9A4R6mWlWxxig6baZTibEbG5FDL0iiH6EMI64zCxjVpJS8B1f9wjw9S7Gs/zP/Tl61LzFgIBIPMMM0yZeJ57atA3O+8QRVpJV4nPiUZMuSPzIdcXjN85FlwWeGTEz6TniCLFYbGOljVnJ1IgniaOqplO+kPVY5bzFWStXWfOe/IXhvL68xHWaQ0hiAYuQIEJBFRsow0aMVp0UC2naT/j4B12/RC6FXBtg5JhHBRpk1w/+B7+7tQrxCS8pnAA6XxznYxgI7QKNmuN8HztO4wQIPgNXestfqQPTn6TXWlr0COjdBi6uW5qyB1zuAANPhmzKrhSkKRQKwPsZfVMO6LsFule93pr7OH0AMtRV6gY4OARGipS95vPurvbe/j3T7O8HwExyxoegtY8AAA8+aVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICB4bWxuczpHSU1QPSJodHRwOi8vd3d3LmdpbXAub3JnL3htcC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgeG1wTU06RG9jdW1lbnRJRD0iZ2ltcDpkb2NpZDpnaW1wOjczZDgyNjA2LThhY2EtNGMzZS1hZDU4LTJjOTAwZDg5NDdmMyIKICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo5MDQ5MTdiMC02ZWM2LTRlMDUtYmY2Ni0wNTIzMzNjYWQ2OTciCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpiZGE1NzZlOS1hZDFiLTRjNDAtOTA1MS0wMjVmNzZlN2VhMGYiCiAgIGRjOkZvcm1hdD0iaW1hZ2UvcG5nIgogICBHSU1QOkFQST0iMi4wIgogICBHSU1QOlBsYXRmb3JtPSJMaW51eCIKICAgR0lNUDpUaW1lU3RhbXA9IjE3MTExODAzMDkyOTY2OTUiCiAgIEdJTVA6VmVyc2lvbj0iMi4xMC4zNiIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1QIDIuMTAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjQ6MDM6MjNUMTQ6NTE6NDkrMDc6MDAiCiAgIHhtcDpNb2RpZnlEYXRlPSIyMDI0OjAzOjIzVDE0OjUxOjQ5KzA3OjAwIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6YjYwMzUwMTAtODY5Yy00MDgyLWJhODQtMzM5ZmIwMDk4NTI3IgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKExpbnV4KSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyNC0wMy0yMVQxNzoyMDoxNSswNzowMCIvPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJzYXZlZCIKICAgICAgc3RFdnQ6Y2hhbmdlZD0iLyIKICAgICAgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo2YWYyNGFjMi0xMDViLTQ2NTktYTM0NC00NTllYzg5YmM2NGIiCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkdpbXAgMi4xMCAoTGludXgpIgogICAgICBzdEV2dDp3aGVuPSIyMDI0LTAzLTIxVDIzOjA4OjQwKzA3OjAwIi8+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InNhdmVkIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvIgogICAgICBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjA0ZmVmZWZhLWExMTMtNGM1MS1hYzM4LTNkODhmZjdkNjNlYyIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iR2ltcCAyLjEwIChMaW51eCkiCiAgICAgIHN0RXZ0OndoZW49IjIwMjQtMDMtMjNUMTQ6NTE6NDkrMDc6MDAiLz4KICAgIDwvcmRmOlNlcT4KICAgPC94bXBNTTpIaXN0b3J5PgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg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 73 | '; 74 | 75 | $self->{content} .= qq{ 76 | 77 | 78 | pgdsat report 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 127 | 128 | 139 | 140 | 141 | 142 | 143 |

PostgreSQL Security Assessement Report
$self->{title}

144 | 145 | }; 146 | 147 | } 148 | 149 | #### 150 | # End the HTML page 151 | #### 152 | sub end_html 153 | { 154 | my $self = shift; 155 | 156 | $self->{content} .= qq{ 157 |

(*) Check not part of the CIS Benchmark

158 | 159 | 164 | 165 | 166 | 167 | 168 | }; 169 | 170 | } 171 | 172 | #### 173 | # Create the resume of the assessment in TEXT format 174 | #### 175 | sub resume_as_text 176 | { 177 | my $self = shift; 178 | 179 | $self->{content} .= "#"x80 . "\n"; 180 | $self->{content} .= "# Summary Table of security checks\n"; 181 | $self->{content} .= "#"x80 . "\n\n"; 182 | foreach my $level (sort { 183 | my @left = (0,0,0); 184 | my @right = (0,0,0); 185 | my @tmp = split(/\./, $a); 186 | for (my $i = 0; $i <= $#tmp; $i++) { 187 | $left[$i] = $tmp[$i]; 188 | } 189 | @tmp = split(/\./, $b); 190 | for (my $i = 0; $i <= $#tmp; $i++) { 191 | $right[$i] = $tmp[$i]; 192 | } 193 | "$left[0]." . sprintf("%02d", $left[1]) . sprintf("%02d", $left[2]) <=> "$right[0]." . sprintf("%02d", $right[1]) . sprintf("%02d", $right[2]) 194 | 195 | } keys %{ $PGDSAT::Labels::AUDIT_LBL{$self->{lang}} } ) 196 | { 197 | next if (!exists $PGDSAT::Labels::AUDIT_LBL{$self->{lang}}{$level}{title}); 198 | my $skip_check = 0; 199 | foreach my $r (@{$self->{remove}}) { 200 | $r =~ s/^\^//; 201 | $skip_check = 1 if (grep(/^$r$/, $level)); 202 | } 203 | next if ($skip_check == 1); 204 | 205 | my $manual = ' (Manual)'; 206 | $manual = '' if (!$PGDSAT::Labels::AUDIT_LBL{$self->{lang}}{$level}{manual}); 207 | $self->{content} .= "$level - $PGDSAT::Labels::AUDIT_LBL{$self->{lang}}{$level}{title}$manual"; 208 | if (!exists $self->{results}{$level} || ($manual && $self->{results}{$level} ne 'FAILURE')) { 209 | $self->{content} .= "\n"; 210 | } else { 211 | $self->{content} .= " => $self->{results}{$level}\n"; 212 | } 213 | } 214 | } 215 | 216 | #### 217 | # Create the resume of the assessment in HTML format 218 | #### 219 | sub resume_as_html 220 | { 221 | my $self = shift; 222 | 223 | $self->{content} .= "

Summary Table of security checks

\n"; 224 | $self->{content} .= "\n"; 225 | $self->{content} .= "\n"; 226 | foreach my $level (sort { 227 | my @left = (0,0,0); 228 | my @right = (0,0,0); 229 | my @tmp = split(/\./, $a); 230 | for (my $i = 0; $i <= $#tmp; $i++) { 231 | $left[$i] = $tmp[$i]; 232 | } 233 | @tmp = split(/\./, $b); 234 | for (my $i = 0; $i <= $#tmp; $i++) { 235 | $right[$i] = $tmp[$i]; 236 | } 237 | "$left[0]." . sprintf("%02d", $left[1]) . sprintf("%02d", $left[2]) <=> "$right[0]." . sprintf("%02d", $right[1]) . sprintf("%02d", $right[2]) 238 | 239 | } keys %{ $PGDSAT::Labels::AUDIT_LBL{$self->{lang}} } ) 240 | { 241 | next if (!exists $PGDSAT::Labels::AUDIT_LBL{$self->{lang}}{$level}{title}); 242 | my $skip_check = 0; 243 | foreach my $r (@{$self->{remove}}) { 244 | $r =~ s/^\^//; 245 | $skip_check = 1 if (grep(/^$r$/, $level)); 246 | } 247 | next if ($skip_check == 1); 248 | 249 | my $manual = ' (Manual)'; 250 | $manual = '' if (!$PGDSAT::Labels::AUDIT_LBL{$self->{lang}}{$level}{manual}); 251 | my $num = () = $level =~ /\./g; 252 | my $tab = " " x ($num * 2); 253 | if ($level =~ /^\d+$/) { 254 | $self->{content} .= "\n"; 255 | } else { 256 | $self->{content} .= "\n"; 269 | } 270 | } 271 | $self->{content} .= "
CIS Benchmark RecommendationSet Correctly
$tab$level$tab$PGDSAT::Labels::AUDIT_LBL{$self->{lang}}{$level}{title}$manual
$tab$level$tab$PGDSAT::Labels::AUDIT_LBL{$self->{lang}}{$level}{title}$manual"; 257 | if (!exists $self->{results}{$level}) { 258 | $self->{content} .= " \n"; 259 | } elsif ($manual && $self->{results}{$level} ne 'FAILURE') { 260 | $self->{content} .= ""; 261 | } else { 262 | if ($self->{results}{$level} eq 'FAILURE') { 263 | $self->{content} .= ""; 264 | } elsif ($self->{results}{$level} eq 'SUCCESS') { 265 | $self->{content} .= ""; 266 | } 267 | } 268 | $self->{content} .= "
\n"; 272 | } 273 | 274 | #### 275 | # Write the report to stdout or file following the options used 276 | #### 277 | sub save_report 278 | { 279 | my $self = shift; 280 | 281 | my $fh; 282 | if ( $self->{output} ne '-' ) { 283 | open $fh, '>', $self->{output} or die "FATAL: can't write to file $self->{output}, $!\n"; 284 | } else { 285 | $fh = \*STDOUT; 286 | } 287 | print $fh $self->{content}; 288 | close $fh if ( $self->{output} ne '-' ); 289 | 290 | return; 291 | } 292 | 293 | 1; 294 | -------------------------------------------------------------------------------- /pgdsat: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | #------------------------------------------------------------------------------- 3 | # Project : Postgresql Database Security Assessment Tool 4 | # Name : pgdsat 5 | # Author : Gilles Darold 6 | # Copyright: Copyright (c) 2024 HexaCluster Corp 7 | # Function : Tool used to perform a reliable and repeatable security assessment 8 | # on PostgreSQL clusters 9 | #------------------------------------------------------------------------------- 10 | # 11 | # This program is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # any later version. 15 | # 16 | # This program is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program. If not, see < http://www.gnu.org/licenses/ >. 23 | # 24 | # 25 | # Some parts are directly copied from the CIS Benchmarks documentation published 26 | # under the Creative Commons License. Please see the below link for the current 27 | # terms of use: https://www.cisecurity.org/terms-and-conditions-table-of-contents 28 | # 29 | #------------------------------------------------------------------------------ 30 | use vars qw($VERSION); 31 | 32 | use strict; 33 | 34 | use PGDSAT; 35 | 36 | use POSIX qw(locale_h); 37 | use Getopt::Long qw(:config no_ignore_case bundling); 38 | 39 | $VERSION = '1.1'; 40 | 41 | setlocale(LC_NUMERIC, 'C'); 42 | setlocale(LC_ALL, 'C'); 43 | 44 | # Flush output immediatly 45 | $| = 1; 46 | 47 | #### 48 | # Process command line options 49 | #### 50 | my @options = ( 51 | 'allow|a=s@', 52 | 'database|d=s', 53 | 'pgdata|D=s', 54 | 'exclude|e=s@', 55 | 'format|f=s', 56 | 'host|h=s', 57 | 'lang|l=s', 58 | 'output|o=s', 59 | 'port|p=i', 60 | 'psql|P=s', 61 | 'title|T=s', 62 | 'version|v!', 63 | 'cluster|V=s', 64 | 'user|U=s', 65 | 'help!', 66 | 'no-pg-version-check!', 67 | 'remove|r=s@', 68 | ); 69 | my %cfg = (); 70 | 71 | my $optres = GetOptions(\%cfg, @options); 72 | die "FATAL: use pgdsat --help\n" if (not $optres); 73 | 74 | # Show version and exit when request 75 | if ($cfg{version}) 76 | { 77 | print "pgdsat v$VERSION\n"; 78 | exit 0; 79 | } 80 | 81 | # Show help and exit when request 82 | &usage if ($cfg{help}); 83 | 84 | # Autoset format with the out file extension if not stdout 85 | if (!$cfg{format} && $cfg{output} ne '-') { 86 | if ($cfg{output} =~ /\.(html|htm)$/) { 87 | $cfg{format} = 'html'; 88 | } elsif ($cfg{output} =~ /\.(text|txt)$/) { 89 | $cfg{format} = 'text'; 90 | } 91 | } 92 | $cfg{format} = 'html' if (!$cfg{format}); 93 | 94 | # Set default title 95 | $cfg{title} ||= 'on ' . &get_hostname(); 96 | 97 | # Verify that psql, systemctl, curl and lsblk are available from PATH 98 | foreach my $c (qw/psql systemctl curl lsblk/) 99 | { 100 | next if ($cfg{'no-pg-version-check'} && $c eq 'curl'); 101 | `which $c 2>/dev/null`; 102 | if ($? != 0) { 103 | die "FATAL: command $c is not available from environment variable \$PATH\n"; 104 | } 105 | } 106 | 107 | #### 108 | # Run security tests following CIS PostgreSQL Benchmark 16 109 | # with additionals checks by HexaCluster Corp 110 | #### 111 | my $pgdsat = new PGDSAT(%cfg); 112 | $pgdsat->run(); 113 | 114 | exit 0; 115 | 116 | #------------------------------------------------------------------------------ 117 | # Methods 118 | #------------------------------------------------------------------------------ 119 | 120 | #### 121 | # Show help message 122 | #### 123 | sub usage 124 | { 125 | print qq{ 126 | Usage: pgdsat [options] 127 | 128 | PostgreSQL Database Security Assessment Tool. 129 | 130 | Options: 131 | 132 | -a | --allow : database to include into the report in parts 4.3 to 4.5. 133 | Can be used multiple time and regexp are supported. 134 | -d | --database: name of the database to connect to PostgreSQL. 135 | -D | --pgdata : path to the PostgreSQL cluster PGDATA to analyze. 136 | -e | --exclude : database to exclude from the report in parts 4.3 to 4.5. 137 | Can be used multiple time and regexp are supported. 138 | -f | --format : output format, can be: text or html. Default: html. 139 | -h | --host : PostgreSQL serveur ip address if not listening on localhost 140 | -l | --lang : language used for the output (en_US, fr_FR, zh_CN). Default: en_US 141 | -o | --output : output file where to write the report. Default stdout. 142 | -p | --port : port where PostgreSQL is listening, default: 5432. 143 | -P | --psql : full path to the psql command if not found in PATH. 144 | -r | --remove : check to remove from the report, it can be used multiple 145 | time. The value can be the number of a check or a regexp. 146 | -T | --title : set title to use to differentiate the reports. Default is 147 | to use "on `hostname`". 148 | -U | --user : PostgreSQL user to use with the psql command. 149 | -v | --version : show version of pgdsat and exist. 150 | -V | --cluster : PostgreSQL Cluster version, ex: 15.4. 151 | --help : show usage and exit. 152 | 153 | --no-pg-version-check : disable check for PostgreSQL minor versions. Useful 154 | when connecting to Internet is not permitted. 155 | Example: 156 | 157 | pgdsat -U postgres -h localhost -d postgres -o report.html 158 | or 159 | pgdsat -U postgres -h localhost -d postgres -f html > report.html 160 | 161 | If you have several PostgreSQL cluster installed you must give the running 162 | version that you want to test: 163 | 164 | pgdsat -U postgres -h localhost -d postgres -f html -V 15.4 > report.html 165 | 166 | If you want, for example, to remove all checks of section 1 from the report: 167 | 168 | pgdsat -U postgres -h localhost -d postgres -V 15.4 -o report.html -r '1.*' 169 | 170 | }; 171 | 172 | exit 0; 173 | } 174 | 175 | #### 176 | # Get hostname to be appended to report's title 177 | #### 178 | sub get_hostname 179 | { 180 | my $hostname = `hostname`; 181 | chomp($hostname); 182 | $hostname ||= 'localhost'; 183 | } 184 | 185 | -------------------------------------------------------------------------------- /rsc/pgdsat-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexaCluster/pgdsat/136cbf47e01a450425a86ed0936196ffefed0841/rsc/pgdsat-logo-small.png -------------------------------------------------------------------------------- /rsc/pgdsat-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexaCluster/pgdsat/136cbf47e01a450425a86ed0936196ffefed0841/rsc/pgdsat-logo.png -------------------------------------------------------------------------------- /sample/report.txt: -------------------------------------------------------------------------------- 1 | POSTGRESQL SECURITY ASSESSEMENT REPORT ON INSPIRON-14 2 | 3 | ################################################################################ 4 | # Summary Table of security checks 5 | ################################################################################ 6 | 7 | 1 - Installation and Patches 8 | 1.1 - Ensure packages are obtained from authorized repositories => SUCCESS 9 | 1.1.1 - PostgreSQL packages installed. (Manual) 10 | 1.1.2 - Ensure packages are obtained from PGDG => SUCCESS 11 | 1.2 - Ensure systemd Service Files Are Enabled => SUCCESS 12 | 1.3 - Ensure Data Cluster Initialized Successfully => SUCCESS 13 | 1.3.1 - Check initialization of the PGDATA => SUCCESS 14 | 1.3.2 - Check version in PGDATA => SUCCESS 15 | 1.3.3 - Ensure Data Cluster have checksum enabled => SUCCESS 16 | 1.3.4 - Ensure WALs and temporary files are not on the same partition as the PGDATA => FAILURE 17 | 1.3.5 - Ensure that the PGDATA partition is encrypted (Manual) 18 | 1.4 - Ensure PostgreSQL versions are up-to-date => FAILURE 19 | 1.5 - Ensure unused PostgreSQL extensions are removed (Manual) 20 | 1.6 - Ensure tablespace location is not inside the PGDATA => FAILURE 21 | 2 - Directory and File Permissions 22 | 2.1 - Ensure the file permissions mask is correct => FAILURE 23 | 2.2 - Check permissions of PGDATA => SUCCESS 24 | 2.3 - List content of PGDATA to check unwanted files and symlinks (Manual) 25 | 2.4 - Check permissions of pg_hba.conf => SUCCESS 26 | 2.5 - Check permissions on Unix Socket => FAILURE 27 | 3 - Logging And Auditing 28 | 3.1 - PostgreSQL Logging => SUCCESS 29 | 3.1.1 - Logging Rationale => SUCCESS 30 | 3.1.2 - Ensure the log destinations are set correctly => SUCCESS 31 | 3.1.3 - Ensure the logging collector is enabled => FAILURE 32 | 3.1.4 - Ensure the log file destination directory is set correctly => SUCCESS 33 | 3.1.5 - Ensure the filename pattern for log files is set correctly (Manual) 34 | 3.1.6 - Ensure the log file permissions are set correctly => SUCCESS 35 | 3.1.7 - Ensure 'log_truncate_on_rotation' is enabled => SUCCESS 36 | 3.1.8 - Ensure the maximum log file lifetime is set correctly (Manual) 37 | 3.1.9 - Ensure the maximum log file size is set correctly (Manual) 38 | 3.1.10 - Ensure the correct syslog facility is selected (Manual) 39 | 3.1.11 - Ensure syslog messages are not suppressed => SUCCESS 40 | 3.1.12 - Ensure syslog messages are not lost due to size => SUCCESS 41 | 3.1.13 - Ensure the program name for PostgreSQL syslog messages is correct (Manual) 42 | 3.1.14 - Ensure the correct messages are written to the server log => SUCCESS 43 | 3.1.15 - Ensure the correct SQL statements generating errors are recorded => SUCCESS 44 | 3.1.16 - Ensure 'debug_print_parse' is disabled => SUCCESS 45 | 3.1.17 - Ensure 'debug_print_rewritten' is disabled => SUCCESS 46 | 3.1.18 - Ensure 'debug_print_plan' is disabled => SUCCESS 47 | 3.1.19 - Ensure 'debug_pretty_print' is enabled => SUCCESS 48 | 3.1.20 - Ensure 'log_connections' is enabled => FAILURE 49 | 3.1.21 - Ensure 'log_disconnections' is enabled => FAILURE 50 | 3.1.22 - Ensure 'log_error_verbosity' is set correctly => FAILURE 51 | 3.1.23 - Ensure 'log_hostname' is set correctly => SUCCESS 52 | 3.1.24 - Ensure 'log_line_prefix' is set correctly => FAILURE 53 | 3.1.25 - Ensure 'log_statement' is set correctly => FAILURE 54 | 3.1.26 - Ensure 'log_timezone' is set correctly => FAILURE 55 | 3.1.27 - Ensure that log_directory is outside the PGDATA => SUCCESS 56 | 3.2 - Ensure the PostgreSQL Audit Extension (pgAudit) is enabled => SUCCESS 57 | 4 - User Access and Authorization 58 | 4.1 - Ensure sudo is configured correctly (Manual) 59 | 4.2 - Ensure excessive administrative privileges are revoked => FAILURE 60 | 4.3 - Ensure excessive function privileges are revoked (Manual) 61 | 4.4 - Ensure excessive DML privileges are revoked (Manual) 62 | 4.5 - Ensure Row Level Security (RLS) is configured correctly (Manual) 63 | 4.6 - Ensure the set_user extension is installed (Manual) => FAILURE 64 | 4.7 - Make use of predefined roles (Manual) 65 | 4.8 - Ensuse the public schema is protected => FAILURE 66 | 5 - Connection and Login 67 | 5.1 - Ensure login via "local" UNIX Domain Socket is configured correctly => FAILURE 68 | 5.2 - Ensure login via "host" TCP/IP Socket is configured correctly => SUCCESS 69 | 5.3 - Ensure Password Complexity is configured => SUCCESS 70 | 5.4 - Ensure authentication timeout and delay are well configured => FAILURE 71 | 5.5 - Ensure SSL is used for client connection => FAILURE 72 | 5.6 - Ensure authorized Ip addresses ranges are not too large => SUCCESS 73 | 5.7 - Ensure specific database and users are used => FAILURE 74 | 5.8 - Ensure superusers are not allowed to connect remotely => SUCCESS 75 | 5.9 - Ensure that 'password_encryption' is correctly set => SUCCESS 76 | 6 - PostgreSQL Settings 77 | 6.1 - Understanding attack vectors and runtime parameters 78 | 6.2 - Ensure 'backend' runtime parameters are configured correctly => FAILURE 79 | 6.3 - Ensure 'Postmaster' runtime parameters are configured correctly (Manual) 80 | 6.4 - Ensure 'SIGHUP' runtime parameters are configured correctly (Manual) 81 | 6.5 - Ensure 'Superuser' runtime parameters are configured correctly (Manual) 82 | 6.6 - Ensure 'User' runtime parameters are configured correctly (Manual) 83 | 6.7 - Ensure FIPS 140-2 OpenSSL cryptography is used => FAILURE 84 | 6.8 - Ensure TLS is enabled and configured correctly => FAILURE 85 | 6.9 - Ensure a cryptographic extension is installed => SUCCESS 86 | 6.10 - Ensure a data anonymization extension is installed => FAILURE 87 | 7 - Replication 88 | 7.1 - Ensure a replication-only user is created and used for streaming replication => FAILURE 89 | 7.2 - Ensure logging of replication commands is configured => FAILURE 90 | 7.3 - Ensure base backups are configured and functional => SUCCESS 91 | 7.4 - Ensure WAL archiving is configured and functional => FAILURE 92 | 7.5 - Ensure streaming replication parameters are configured correctly => FAILURE 93 | 8 - Special Configuration Considerations 94 | 8.1 - Ensure PostgreSQL subdirectory locations are outside the data cluster => SUCCESS 95 | 8.2 - Ensure the backup and restore tool, 'pgBackRest', is installed and configured => SUCCESS 96 | 8.3 - Ensure miscellaneous configuration settings are correct (Manual) 97 | 98 | 99 | ################################################################################ 100 | # Detailled security assessment 101 | ################################################################################ 102 | 103 | #-------------------------------------------------------------------------------- 104 | # 1 - Installation and Patches 105 | #-------------------------------------------------------------------------------- 106 | # 1.1 - Ensure packages are obtained from authorized repositories 107 | Identify and inspect configured repositories to ensure they are all valid and authorized sources of packages. 108 | 109 | # 1.1.1 - PostgreSQL packages installed. (Manual) 110 | Inspect installed package to ensure they are all valid and authorized packages. 111 | 112 | DATA: postgresql-11 11.22-2.pgdg22.04+1 amd64 The World's Most Advanced Open Source Relational Database 113 | DATA: postgresql-12 12.18-1.pgdg22.04+1 amd64 The World's Most Advanced Open Source Relational Database 114 | DATA: postgresql-13 13.14-1.pgdg22.04+1 amd64 The World's Most Advanced Open Source Relational Database 115 | DATA: postgresql-14 14.11-1.pgdg22.04+1 amd64 The World's Most Advanced Open Source Relational Database 116 | DATA: postgresql-14-hypopg 1.4.0-2.pgdg22.04+1 amd64 PostgreSQL extension adding support for hypothetical indexes. 117 | DATA: postgresql-14-postgis-3 3.4.2+dfsg-1.pgdg22.04+1 amd64 Geographic objects support for PostgreSQL 14 118 | DATA: postgresql-14-postgis-3-scripts 3.4.2+dfsg-1.pgdg22.04+1 all Geographic objects support for PostgreSQL 14 -- SQL scripts 119 | DATA: postgresql-15 15.6-1.pgdg22.04+1 amd64 The World's Most Advanced Open Source Relational Database 120 | DATA: postgresql-15-citus-12.0 12.0.1.citus-1 amd64 sharding and distributed joins for PostgreSQL 121 | DATA: postgresql-15-postgis-3 3.4.2+dfsg-1.pgdg22.04+1 amd64 Geographic objects support for PostgreSQL 15 122 | DATA: postgresql-15-postgis-3-scripts 3.4.2+dfsg-1.pgdg22.04+1 all Geographic objects support for PostgreSQL 15 -- SQL scripts 123 | DATA: postgresql-15-tds-fdw 2.0.3-3.pgdg22.04+1 amd64 PostgreSQL foreign data wrapper for TDS databases 124 | DATA: postgresql-16 16.2-1.pgdg22.04+1 amd64 The World's Most Advanced Open Source Relational Database 125 | DATA: postgresql-16-postgis-3 3.4.2+dfsg-1.pgdg22.04+1 amd64 Geographic objects support for PostgreSQL 16 126 | DATA: postgresql-16-postgis-3-scripts 3.4.2+dfsg-1.pgdg22.04+1 all Geographic objects support for PostgreSQL 16 -- SQL scripts 127 | DATA: postgresql-9.3 9.3.25-9.pgdg22.04+3 amd64 object-relational SQL database, version 9.3 server 128 | DATA: postgresql-9.4 9.4.26-8.pgdg22.04+3 amd64 object-relational SQL database, version 9.4 server 129 | 130 | # 1.1.2 - Ensure packages are obtained from PGDG 131 | PostgreSQL packages not supported by the PostgreSQL community are generaly not recommended. 132 | 133 | SUCCESS: Test passed 134 | 135 | # 1.2 - Ensure systemd Service Files Are Enabled 136 | Check that the PostgreSQL systemd service is enabled. Enabling the systemd PostgreSQL service ensures that the database service is active when at system startup and reboot. This check is not done if Patroni is installed, in this case the start of PostgreSQL is handled by Patroni. (HexaCLuster) 137 | 138 | SUCCESS: Test passed 139 | 140 | # 1.3 - Ensure Data Cluster Initialized Successfully 141 | PostgreSQL enforces ownership and permissions of the data cluster such that the data cluster cannot be accessed by other UNIX user accounts and the data cluster cannot owned by root. 142 | 143 | # 1.3.1 - Check initialization of the PGDATA 144 | The command initdb might have been run before starting PostgreSQL, verify that this is the case. 145 | 146 | SUCCESS: Test passed 147 | 148 | # 1.3.2 - Check version in PGDATA 149 | PostgreSQL maintain a file called PG_VERSION in the base directory, verify that . 150 | 151 | SUCCESS: Test passed 152 | 153 | # 1.3.3 - Ensure Data Cluster have checksum enabled 154 | When checksum are not enabled, silent data corruption can not be detected by PostgreSQL. Verify that they are enabled. (*) 155 | 156 | SUCCESS: Test passed 157 | 158 | # 1.3.4 - Ensure WALs and temporary files are not on the same partition as the PGDATA 159 | The PostgreSQL cluster is organized to carry out specific tasks in subdirectories. For the purposes of performance, reliability, and security some of these subdirectories should be relocated outside the data cluster. (*) 160 | 161 | WARNING: Subdirectory pg_wal is not on a separate partition than the PGDATA . 162 | 163 | WARNING: Subdirectory for temporary file is not on a separate partition than the PGDATA. 164 | 165 | # 1.3.5 - Ensure that the PGDATA partition is encrypted (Manual) 166 | PostgreSQL storage encryption can be performed at the file system level or the block level, for example using LUKS. This mechanism prevents unencrypted data from being read from the drives if the drives or the entire computer is stolen. This does not protect against attacks while the file system is mounted, because when mounted, the operating system provides an unencrypted view of the data. (*) 167 | 168 | DATA: NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS 169 | DATA: nvme0n1 170 | DATA: ├─nvme0n1p1 vfat FAT32 9562-6409 449,5M 12% /boot/efi 171 | DATA: ├─nvme0n1p2 ext4 1.0 b094a4f8-e122-4507-aef2-b840488970a9 1,1G 27% /boot 172 | DATA: └─nvme0n1p3 crypto_LUKS 2 7942f063-81b2-412f-a9d7-9b085d6635b2 173 | DATA: └─nvme0n1p3_crypt LVM2_member LVM2 001 6v9nXI-HLMs-2b9u-Tbqx-hw51-JAsl-RjaiHL 174 | DATA: ├─vgubuntu-root ext4 1.0 dc73afb5-86bd-4405-aa11-576f1f023fcb 18,6G 91% /var/snap/firefox/common/host-hunspell 175 | DATA: │ / 176 | DATA: └─vgubuntu-swap_1 swap 1 e5cdf1a9-0ed6-4573-9c4b-8e8e1f918ece [SWAP] 177 | 178 | # 1.4 - Ensure PostgreSQL versions are up-to-date 179 | CRITICAL: This PostgreSQL version, v15.1, is not the last one of this branch (15.6) 180 | 181 | INFO: See Why upgrade. 182 | 183 | # 1.5 - Ensure unused PostgreSQL extensions are removed (Manual) 184 | # 1.5.1 - contrib_regression 185 | DATA: Name|Version|Schema|Description 186 | DATA: plpgsql|1.0|pg_catalog|PL/pgSQL procedural language 187 | 188 | # 1.5.2 - gilles 189 | DATA: Name|Version|Schema|Description 190 | DATA: btree_gist|1.7|public|support for indexing common datatypes in GiST 191 | DATA: credcheck|2.6.0|public|credcheck - postgresql plain text credential checker 192 | DATA: oracle_fdw|1.2|public|foreign data wrapper for Oracle access 193 | DATA: orafce|4.3|public|Functions and operators that emulate a subset of functions and packages from the Oracle RDBMS 194 | DATA: pg_stat_statements|1.10|public|track planning and execution statistics of all SQL statements executed 195 | DATA: pg_subxact_counters|1.0|public| 196 | DATA: pg_wait_sampling|1.1|public|sampling based statistics of wait events 197 | DATA: pgcrypto|1.3|public|cryptographic functions 198 | DATA: plperl|1.0|pg_catalog|PL/Perl procedural language 199 | DATA: plperlu|1.0|pg_catalog|PL/PerlU untrusted procedural language 200 | DATA: plpgsql|1.0|pg_catalog|PL/pgSQL procedural language 201 | DATA: plpgsql_enc|2.0|pg_catalog|Encrypted PL/pgSQL procedural language 202 | DATA: postgis|3.4.2|public|PostGIS geometry and geography spatial types and functions 203 | DATA: postgres_fdw|1.1|public|foreign-data wrapper for remote PostgreSQL servers 204 | 205 | # 1.6 - Ensure tablespace location is not inside the PGDATA 206 | WARNING: Tablespace location /var/lib/postgresql/15/main/TB1 should not be inside the data directory. 207 | 208 | #-------------------------------------------------------------------------------- 209 | # 2 - Directory and File Permissions 210 | #-------------------------------------------------------------------------------- 211 | # 2.1 - Ensure the file permissions mask is correct 212 | The postgres system user should have a restrictive umask for file creation so that other UNIX users can not look at anything. 213 | 214 | CRITICAL: The umask must be 0077 or more restrictive for the postgres user. Currently it is set to 0002. 215 | 216 | # 2.2 - Check permissions of PGDATA 217 | The data cluster Unix permissions must be 0700 (*). 218 | 219 | SUCCESS: Test passed 220 | 221 | # 2.3 - List content of PGDATA to check unwanted files and symlinks (Manual) 222 | The content of the PGDATA must be generated by PostgreSQL itself except custom configuration files. (*). 223 | 224 | DATA: total 108 225 | DATA: drwx------ 21 postgres postgres 4096 avril 17 16:39 . 226 | DATA: drwxr-xr-x 3 postgres postgres 4096 nov. 6 2022 .. 227 | DATA: drwx------ 32 postgres postgres 4096 avril 17 17:48 base 228 | DATA: drwx------ 2 postgres postgres 4096 avril 17 18:22 global 229 | DATA: drwx------ 2 postgres postgres 4096 mars 12 10:46 log 230 | DATA: drwx------ 2 postgres postgres 4096 nov. 6 2022 pg_commit_ts 231 | DATA: drwx------ 2 postgres postgres 4096 nov. 6 2022 pg_dynshmem 232 | DATA: drwx------ 4 postgres postgres 4096 avril 17 18:24 pg_logical 233 | DATA: drwx------ 4 postgres postgres 4096 nov. 6 2022 pg_multixact 234 | DATA: drwx------ 2 postgres postgres 4096 nov. 6 2022 pg_notify 235 | DATA: -rw------- 1 postgres postgres 8192 avril 15 21:02 pg_password_history 236 | DATA: drwx------ 2 postgres postgres 4096 nov. 6 2022 pg_replslot 237 | DATA: drwx------ 2 postgres postgres 4096 nov. 6 2022 pg_serial 238 | DATA: drwx------ 2 postgres postgres 4096 mars 14 20:27 pg_snapshots 239 | DATA: drwx------ 2 postgres postgres 4096 avril 17 09:40 pg_stat 240 | DATA: drwx------ 2 postgres postgres 4096 févr. 16 23:32 pg_stat_tmp 241 | DATA: drwx------ 2 postgres postgres 4096 déc. 4 18:14 pg_subtrans 242 | DATA: drwx------ 2 postgres postgres 4096 avril 17 16:40 pg_tblspc 243 | DATA: drwx------ 2 postgres postgres 4096 nov. 6 2022 pg_twophase 244 | DATA: -rw------- 1 postgres postgres 3 nov. 6 2022 PG_VERSION 245 | DATA: drwx------ 3 postgres postgres 4096 avril 15 21:02 pg_wal 246 | DATA: drwx------ 2 postgres postgres 4096 déc. 1 19:32 pg_xact 247 | DATA: -rw------- 1 postgres postgres 88 nov. 6 2022 postgresql.auto.conf 248 | DATA: -rw------- 1 postgres postgres 130 avril 17 09:40 postmaster.opts 249 | DATA: -rw------- 1 postgres postgres 100 avril 17 17:49 postmaster.pid 250 | DATA: drwx------ 3 postgres postgres 4096 avril 17 16:40 TB1 251 | 252 | # 2.4 - Check permissions of pg_hba.conf 253 | The pg_hba.conf UNIX permission must be 0640 or 0600, especially when it is stored outside the PGDATA (*). 254 | 255 | SUCCESS: Test passed 256 | 257 | # 2.5 - Check permissions on Unix Socket 258 | The default permissions are 0777, meaning anyone can connect. Reasonable alternatives are 0770 (only user and group, see also unix_socket_group) and 0700 (only user). (*). 259 | 260 | WARNING: Permission on Unix socket /var/run/postgresql/.s.PGSQL.5432 should be more restrictive, for example: 0770 or 0700. Currently it is set to 0777. 261 | 262 | #-------------------------------------------------------------------------------- 263 | # 3 - Logging And Auditing 264 | #-------------------------------------------------------------------------------- 265 | # 3.1 - PostgreSQL Logging 266 | This section provides guidance with respect to PostgreSQL's logging behavior as it applies to security and auditing. 267 | 268 | # 3.1.1 - Logging Rationale 269 | Having an audit trail is an important feature of any relational database system. You want enough detail to describe when an event of interest has started and stopped, what the event is/was, the event's cause, and what the event did/is doing to the system. Ideally, the logged information is in a format permitting further analysis giving us new perspectives and insight. 270 | 271 | # 3.1.2 - Ensure the log destinations are set correctly 272 | If log_destination is not set, then any log messages generated by the core 273 | PostgreSQL processes will be lost. 274 | 275 | SUCCESS: Test passed 276 | 277 | # 3.1.3 - Ensure the logging collector is enabled 278 | The logging collector approach is often more useful than logging to syslog, since some types of messages might not appear in syslog output. One common example is dynamic-linker failure message; another may be error messages produced by scripts such as archive_command. 279 | 280 | CRITICAL: Setting 'logging_collector' must be enabled when 'log_destination' is not set to syslog, logging will be lost. 281 | 282 | # 3.1.4 - Ensure the log file destination directory is set correctly 283 | If log_directory is not set, it is interpreted as the absolute path '/' and PostgreSQL will attempt to write its logs there 284 | 285 | SUCCESS: Test passed 286 | 287 | # 3.1.5 - Ensure the filename pattern for log files is set correctly (Manual) 288 | If log_filename is not set, then the value of log_directory is appended to an empty string and PostgreSQL will fail to start as it will try to write to a directory instead of a file. 289 | 290 | SUCCESS: Test passed 291 | 292 | # 3.1.6 - Ensure the log file permissions are set correctly 293 | Log files often contain sensitive data. Allowing unnecessary access to log files may inadvertently expose sensitive data to unauthorized personnel. 294 | 295 | SUCCESS: Test passed 296 | 297 | # 3.1.7 - Ensure 'log_truncate_on_rotation' is enabled 298 | If this setting is disabled, pre-existing log files will be appended to if log_filename is configured in such a way that static or recurring names are generated. 299 | 300 | SUCCESS: Test passed 301 | 302 | # 3.1.8 - Ensure the maximum log file lifetime is set correctly (Manual) 303 | Current best practices advise log rotation at least daily, but your organization's logging policy should dictate your rotation schedule. 304 | 305 | SUCCESS: Test passed 306 | 307 | # 3.1.9 - Ensure the maximum log file size is set correctly (Manual) 308 | If this is set to zero, the size-triggered creation of new log files is disabled. This will prevent automatic log file rotation when files become too large, which could put log data at increased risk of loss (unless age-based rotation is configured). 309 | 310 | SUCCESS: Test passed 311 | 312 | # 3.1.10 - Ensure the correct syslog facility is selected (Manual) 313 | If not set to the appropriate facility, the PostgreSQL log messages may be intermingled with other applications log messages, incorrectly routed, or potentially dropped (depending on your syslog configuration). 314 | 315 | SUCCESS: Test passed 316 | 317 | # 3.1.11 - Ensure syslog messages are not suppressed 318 | If disabled, messages sent to Syslog could be suppressed and not logged. While a message is emitted stating that a given message was repeated and suppressed, the timestamp associated with these suppressed messages is lost, potentially damaging the recreation of an incident timeline. 319 | 320 | SUCCESS: Test passed 321 | 322 | # 3.1.12 - Ensure syslog messages are not lost due to size 323 | Depending on the Syslog server in use, log messages exceeding 1024 bytes may be lost or, potentially, cause the Syslog server processes to abort. 324 | 325 | SUCCESS: Test passed 326 | 327 | # 3.1.13 - Ensure the program name for PostgreSQL syslog messages is correct (Manual) 328 | If this is not set correctly, it may be difficult or impossible to distinguish PostgreSQL messages from other messages in Syslog logs. 329 | 330 | SUCCESS: Test passed 331 | 332 | # 3.1.14 - Ensure the correct messages are written to the server log 333 | If this is not set to the correct value, too many or too few messages may be written to the server log. 334 | 335 | SUCCESS: Test passed 336 | 337 | # 3.1.15 - Ensure the correct SQL statements generating errors are recorded 338 | If this is not set to the correct value, too many erring or too few erring SQL statements may be written to the server log. 339 | 340 | SUCCESS: Test passed 341 | 342 | # 3.1.16 - Ensure 'debug_print_parse' is disabled 343 | Enabling any of the DEBUG printing variables may cause the logging of sensitive information that would otherwise be omitted based on the configuration of the other logging settings. 344 | 345 | SUCCESS: Test passed 346 | 347 | # 3.1.17 - Ensure 'debug_print_rewritten' is disabled 348 | Enabling any of the DEBUG printing variables may cause the logging of sensitive information that would otherwise be omitted based on the configuration of the other logging settings. 349 | 350 | SUCCESS: Test passed 351 | 352 | # 3.1.18 - Ensure 'debug_print_plan' is disabled 353 | Enabling any of the DEBUG printing variables may cause the logging of sensitive information that would otherwise be omitted based on the configuration of the other logging settings. 354 | 355 | SUCCESS: Test passed 356 | 357 | # 3.1.19 - Ensure 'debug_pretty_print' is enabled 358 | If this setting is disabled, the "compact" format is used instead, significantly reducing the readability of the DEBUG statement log messages. 359 | 360 | SUCCESS: Test passed 361 | 362 | # 3.1.20 - Ensure 'log_connections' is enabled 363 | PostgreSQL does not maintain an internal record of attempted connections to the database for later auditing. It is only by enabling the logging of these attempts that one can determine if unexpected attempts are being made. 364 | 365 | WARNING: Setting 'log_connections' should be enabled. 366 | 367 | # 3.1.21 - Ensure 'log_disconnections' is enabled 368 | PostgreSQL does not maintain the beginning or ending of a connection internally for later review. It is only by enabling the logging of these that one can examine connections for failed attempts, 'over long' duration, or other anomalies. 369 | 370 | WARNING: Setting 'log_disconnections' should be enabled. 371 | 372 | # 3.1.22 - Ensure 'log_error_verbosity' is set correctly 373 | If this is not set to the correct value, too many details or too few details may be logged. 374 | 375 | WARNING: Setting 'log_error_verbosity' should be set to 'verbose'. 376 | 377 | # 3.1.23 - Ensure 'log_hostname' is set correctly 378 | Depending on your hostname resolution setup, enabling this setting might impose a non-negligible performance penalty. Additionally, the IP addresses that are logged can be resolved to their DNS names when reviewing the logs (unless dynamic hostnames are being used as part of your DHCP setup). 379 | 380 | SUCCESS: Test passed 381 | 382 | # 3.1.24 - Ensure 'log_line_prefix' is set correctly 383 | Properly setting log_line_prefix allows for adding additional information to each log entry (such as the user, or the database). Said information may then be of use in auditing or security reviews. The prefix should at least include '%m [%p]: db=%d,user=%u,app=%a,client=%h ' (for logging to stderr) and for syslog logging, the prefix should include 'user=%u,db=%d,app=%a,client=%h '. 384 | 385 | WARNING: Setting 'log_line_prefix' should containt at least '%m [%p]: db=%d,user=%u,app=%a,client=%h ' (for stderr logging). For syslog logging, the prefix should include 'user=%u,db=%d,app=%a,client=%h '. 386 | 387 | # 3.1.25 - Ensure 'log_statement' is set correctly 388 | Setting log_statement to align with your organization's security and logging policies facilitates later auditing and review of database activities. 389 | 390 | WARNING: Setting 'log_statement' should at least be set to 'ddl'. 391 | 392 | # 3.1.26 - Ensure 'log_timezone' is set correctly 393 | Log entry timestamps should be configured for an appropriate time zone as defined by your organization's logging policy to ensure a lack of confusion around when a logged event occurred. Note that this setting affects only the timestamps present in the logs. 394 | 395 | WARNING: Setting 'log_timezone' should be set to 'GMT' or 'UTC'. 396 | 397 | # 3.1.27 - Ensure that log_directory is outside the PGDATA 398 | Best practice is to not write PostgreSQL logs into the PGDATA for performances reason and disk space use. (*) 399 | 400 | SUCCESS: Test passed 401 | 402 | # 3.2 - Ensure the PostgreSQL Audit Extension (pgAudit) is enabled 403 | The PostgreSQL Audit Extension (pgAudit) provides detailed session and/or object audit logging via the standard PostgreSQL logging facility. The goal of pgAudit is to provide PostgreSQL users with the capability to produce audit logs often required to comply with government, financial, or ISO certifications. 404 | 405 | WARNING: PostgreSQL extension pgAudit should be used. 406 | 407 | #-------------------------------------------------------------------------------- 408 | # 4 - User Access and Authorization 409 | #-------------------------------------------------------------------------------- 410 | These authorizations must be structured to block unauthorized use and/or corruption of vital data and services by setting restrictions on user capabilities. 411 | 412 | # 4.2 - Ensure excessive administrative privileges are revoked 413 | By not restricting global administrative commands to superusers only, regular users granted excessive privileges may execute administrative commands with unintended and undesirable results. 414 | 415 | WARNING: There are more than one PostgreSQL superuser. 416 | 417 | DATA: Role|Attributs|Description 418 | DATA: gilles|Superuser|{}| 419 | DATA: postgres|Superuser, Create role, Create DB, Replication, Bypass RLS|{}| 420 | 421 | # 4.3 - Ensure excessive function privileges are revoked (Manual) 422 | Functions in PostgreSQL can be created with the SECURITY DEFINER option. When SECURITY DEFINER functions are executed by a user, said function is run with the privileges of the user who created it, not the user who is running it. List of the functions with SECURITY DEFINER option not associated to an extension, per database. 423 | 424 | # 4.3.1 - gilles 425 | DATA: oid|nspname|proname|rolname|prosecdef|proconfig|proacl 426 | DATA: 511628|public|add|gilles|t|| 427 | 428 | # 4.4 - Ensure excessive DML privileges are revoked (Manual) 429 | Excessive DML grants can lead to unprivileged users changing or deleting information without proper authorization. 430 | 431 | # 4.4.1 - gilles 432 | DATA: schema|table|user|select|insert|update|delete 433 | DATA: public|t3|dump_anon|t|f|f|f 434 | DATA: public|customer|dump_anon|t|f|f|f 435 | DATA: public|spatial_ref_sys|dump_anon|t|f|f|f 436 | DATA: public|spatial_ref_sys|test|t|f|f|f 437 | DATA: public|spatial_ref_sys|testuser|t|f|f|f 438 | DATA: public|spatial_ref_sys|hr|t|f|f|f 439 | DATA: public|spatial_ref_sys|u01|t|f|f|f 440 | DATA: public|spatial_ref_sys|pgtt_user1|t|f|f|f 441 | DATA: public|spatial_ref_sys|user|t|f|f|f 442 | DATA: public|spatial_ref_sys|dolibarr|t|f|f|f 443 | DATA: public|tab|dump_anon|t|f|f|f 444 | DATA: utl_file|utl_file_dir|dump_anon|t|f|f|f 445 | DATA: utl_file|utl_file_dir|test|t|f|f|f 446 | DATA: utl_file|utl_file_dir|testuser|t|f|f|f 447 | DATA: utl_file|utl_file_dir|hr|t|f|f|f 448 | DATA: utl_file|utl_file_dir|u01|t|f|f|f 449 | DATA: utl_file|utl_file_dir|pgtt_user1|t|f|f|f 450 | DATA: utl_file|utl_file_dir|user|t|f|f|f 451 | DATA: utl_file|utl_file_dir|dolibarr|t|f|f|f 452 | 453 | # 4.5 - Ensure Row Level Security (RLS) is configured correctly (Manual) 454 | If RLS policies and privileges are not configured correctly, users could perform actions on tables that they are not authorized to perform, such as inserting, updating, or deleting rows. List tables with RLS enabled. 455 | 456 | SUCCESS: Test passed 457 | 458 | # 4.6 - Ensure the set_user extension is installed (Manual) 459 | Even when reducing and limiting the access to the superuser role, it is still difficult to determine who accessed the superuser role and what actions were taken using that role. As such, it is ideal to prevent anyone from logging in as the superuser and forcing them to escalate their role. The set_user extension allows for this setup. 460 | 461 | WARNING: PostgreSQL extension set_user should be used. 462 | 463 | DATA: rolname|roloid|rolcanlogin|rolsuper|rolparents 464 | DATA: gilles|16389|t|t|{} 465 | DATA: postgres|10|t|t|{} 466 | 467 | # 4.7 - Make use of predefined roles (Manual) 468 | In keeping with the principle of least privilege, judicious use of the PostgreSQL predefined roles can greatly limit the access to privileged, or superuser, access. 469 | 470 | DATA: rolname|roloid|rolcanlogin|rolsuper|rolparents 471 | DATA: pg_monitor|3373|f|f|{pg_read_all_settings} 472 | DATA: pg_monitor|3373|f|f|{pg_read_all_stats} 473 | DATA: pg_monitor|3373|f|f|{pg_stat_scan_tables} 474 | 475 | # 4.8 - Ensuse the public schema is protected 476 | Privileges on the PostgreSQL default public schema must be restricted to some users, grant to public users must be removed or the schema dropped. 477 | 478 | # 4.8.1 - contrib_regression 479 | WARNING: Schema public can be used by anyone in database contrib_regression. 480 | 481 | DATA: nspname|nspowner|nspacl 482 | DATA: public|6171|{pg_database_owner=UC/pg_database_owner,=U/pg_database_owner} 483 | 484 | # 4.8.2 - gilles 485 | WARNING: Schema public can be used by anyone in database gilles. 486 | 487 | DATA: nspname|nspowner|nspacl 488 | DATA: public|6171|{pg_database_owner=UC/pg_database_owner,=U/pg_database_owner,dump_anon=U/pg_database_owner} 489 | 490 | #-------------------------------------------------------------------------------- 491 | # 5 - Connection and Login 492 | #-------------------------------------------------------------------------------- 493 | The restrictions on client/user connections to the PostgreSQL database blocks unauthorized access to data and services by setting access rules. These security measures help to ensure that successful logins cannot be easily made through brute-force password attacks, replaying the password hash, or intuited by clever social engineering exploits. 494 | 495 | # 5.1 - Ensure login via "local" UNIX Domain Socket is configured correctly 496 | A remote host login, via SSH, is arguably the most secure means of remotely accessing and administering the PostgreSQL server. Once connected to the PostgreSQL server, using the psql client, via UNIX DOMAIN SOCKETS, while using the peer authentication method is the most secure mechanism available for local database connections. 497 | 498 | CRITICAL: The use of the "trust" authentication method must not be used. See line 96 in file /etc/postgresql/15/main/pg_hba.conf. 499 | 500 | DATA: local all all trust 501 | 502 | INFO: Use peer or any of the external authentication method (gss, sspi, pam, ldap, radius or cert) instead. 503 | 504 | # 5.2 - Ensure login via "host" TCP/IP Socket is configured correctly 505 | A large number of authentication methods are available for hosts connecting using TCP/IP sockets. Methods trust, password, and ident are not to be used for remote logins. Method md5 used to be the most popular and can be used in both encrypted and unencrypted sessions, however, it is vulnerable to packet replay attacks. It is recommended that scram-sha-256 be used instead of md5. Use of the gss, sspi, pam, ldap, radius, and cert methods are dependent upon the availability of external authenticating processes/services and thus are not covered here. 506 | 507 | CRITICAL: The use of the "trust" authentication method must not be used. See line 100 in file /etc/postgresql/15/main/pg_hba.conf. 508 | 509 | DATA: host all all 192.168.43.0/24 trust 510 | 511 | INFO: Use scram-sha-256 or any of the external authentication method (gss, sspi, pam, ldap, radius or cert) instead. 512 | 513 | # 5.3 - Ensure Password Complexity is configured 514 | Having strong password management for your locally-authenticated PostgreSQL accounts will protect against attackers' brute force techniques. This is important especially if external authentication is not possible to implement due to application requirements or restrictions. 515 | 516 | SUCCESS: Test passed 517 | 518 | # 5.4 - Ensure authentication timeout and delay are well configured 519 | Authentication timeout is the maximum amount of time allowed to complete client authentication. If a would-be client has not completed the authentication protocol in this much time, the server closes the connection. This prevents hung clients from occupying a connection indefinitely. Authentication delay causes the server to pause briefly before reporting authentication failure, to make brute-force attacks on database passwords more difficult. (*) 520 | 521 | WARNING: You should add an authentication failure delay to prevent brute force attack. See PostgreSQL extension credcheck or auth_delay. 522 | 523 | # 5.5 - Ensure SSL is used for client connection 524 | All remote client connection should be encrypted and non encrypted connexion should be reject to not permit data sniffing on the network. (*) 525 | 526 | CRITICAL: Use of ssl encryption for all remote connection should be used, see "hostssl" and "hostgssenc" connection type. 527 | 528 | # 5.6 - Ensure authorized Ip addresses ranges are not too large 529 | Allowing a too large range of Ip addresses to connect to PostgreSQL cluster multiply the risks unnecessarily. (*) 530 | 531 | # 5.7 - Ensure specific database and users are used 532 | The keyword "all" in the database and user part of the pg_hba.conf rules can allow any user to connect to any database, it is recommended to restrict the connection to specific user and database. (*) 533 | 534 | WARNING: You should be more specific and give the database and users allowed to connect, not "all". See line scram-sha-256 in file 98. 535 | 536 | DATA: host all all 127.0.0.1/32 scram-sha-256 537 | 538 | WARNING: You should be more specific and give the database and users allowed to connect, not "all". See line scram-sha-256 in file 99. 539 | 540 | DATA: host all all 172.18.0.3/32 scram-sha-256 541 | 542 | WARNING: You should be more specific and give the database and users allowed to connect, not "all". See line trust in file 100. 543 | 544 | DATA: host all all 192.168.43.0/24 trust 545 | 546 | WARNING: You should be more specific and give the database and users allowed to connect, not "all". See line scram-sha-256 in file 102. 547 | 548 | DATA: host all all ::1/128 scram-sha-256 549 | 550 | WARNING: You should be more specific and give the database and users allowed to connect, not "all". See line scram-sha-256 in file 106. 551 | 552 | DATA: host replication all 127.0.0.1/32 scram-sha-256 553 | 554 | WARNING: You should be more specific and give the database and users allowed to connect, not "all". See line scram-sha-256 in file 107. 555 | 556 | DATA: host replication all ::1/128 scram-sha-256 557 | 558 | # 5.8 - Ensure superusers are not allowed to connect remotely 559 | Allowing a PostgreSQL superuser to connect to a database from a remote host is dangerous, best is to only allow the superuser(s) to connect locally with a peer authentication. If some advanced privileges are required, best is to use the PostgreSQL predefined roles. (*) 560 | 561 | SUCCESS: Test passed 562 | 563 | # 5.9 - Ensure that 'password_encryption' is correctly set 564 | PostgreSQL allow to set password encryption, default is now 'scram-sha-256' but it can be set to 'md5' which is insecure. (*) 565 | 566 | SUCCESS: Test passed 567 | 568 | #-------------------------------------------------------------------------------- 569 | # 6 - PostgreSQL Settings 570 | #-------------------------------------------------------------------------------- 571 | # 6.2 - Ensure 'backend' runtime parameters are configured correctly 572 | A denial of service is possible by denying the use of indexes and by slowing down client access to an unreasonable level. Unsanctioned behavior can be introduced by introducing rogue libraries which can then be called in a database session. Logging can be altered and obfuscated inhibiting root cause analysis. All changes made on this level will affect the overall behavior of the server. These changes can only be affected by a server restart after the parameters have been altered in the configuration files. 573 | 574 | CRITICAL: Setting 'log_connections' must be enabled. 575 | 576 | CRITICAL: Setting 'log_disconnections' must be enabled. 577 | 578 | # 6.3 - Ensure 'Postmaster' runtime parameters are configured correctly (Manual) 579 | The postmaster process is the supervisory process that assigns a backend process to an incoming client connection. The postmaster manages key runtime parameters that are either shared by all backend connections or needed by the postmaster process itself to run. The following parameters can only be set at server start by the owner of the PostgreSQL server process and cluster, typically the UNIX user account postgres. Therefore, all exploits require the successful compromise of either that UNIX account or the postgres superuser account itself. 580 | 581 | DATA: name|setting 582 | DATA: archive_mode|off 583 | DATA: autovacuum_freeze_max_age|200000000 584 | DATA: autovacuum_max_workers|3 585 | DATA: autovacuum_multixact_freeze_max_age|400000000 586 | DATA: bonjour|off 587 | DATA: bonjour_name| 588 | DATA: cluster_name|15/main 589 | DATA: config_file|/etc/postgresql/15/main/postgresql.conf 590 | DATA: credcheck.auth_failure_cache_size|1024 591 | DATA: credcheck.history_max_size|65535 592 | DATA: data_directory|/var/lib/postgresql/15/main 593 | DATA: data_sync_retry|off 594 | DATA: dynamic_shared_memory_type|posix 595 | DATA: event_source|PostgreSQL 596 | DATA: external_pid_file|/var/run/postgresql/15-main.pid 597 | DATA: hba_file|/etc/postgresql/15/main/pg_hba.conf 598 | DATA: hot_standby|on 599 | DATA: huge_pages|try 600 | DATA: huge_page_size|0 601 | DATA: ident_file|/etc/postgresql/15/main/pg_ident.conf 602 | DATA: ignore_invalid_pages|off 603 | DATA: jit_provider|llvmjit 604 | DATA: listen_addresses|* 605 | DATA: logging_collector|off 606 | DATA: max_connections|100 607 | DATA: max_files_per_process|1000 608 | DATA: max_locks_per_transaction|64 609 | DATA: max_logical_replication_workers|4 610 | DATA: max_pred_locks_per_transaction|64 611 | DATA: max_prepared_transactions|0 612 | DATA: max_replication_slots|10 613 | DATA: max_wal_senders|10 614 | DATA: max_worker_processes|8 615 | DATA: min_dynamic_shared_memory|0 616 | DATA: old_snapshot_threshold|-1 617 | DATA: port|5432 618 | DATA: recovery_target| 619 | DATA: recovery_target_action|pause 620 | DATA: recovery_target_inclusive|on 621 | DATA: recovery_target_lsn| 622 | DATA: recovery_target_name| 623 | DATA: recovery_target_time| 624 | DATA: recovery_target_timeline|latest 625 | DATA: recovery_target_xid| 626 | DATA: shared_buffers|16384 627 | DATA: shared_memory_type|mmap 628 | DATA: shared_preload_libraries|credcheck 629 | DATA: superuser_reserved_connections|3 630 | DATA: track_activity_query_size|1024 631 | DATA: track_commit_timestamp|off 632 | DATA: unix_socket_directories|/var/run/postgresql 633 | DATA: unix_socket_group| 634 | DATA: unix_socket_permissions|0777 635 | DATA: wal_buffers|512 636 | DATA: wal_decode_buffer_size|524288 637 | DATA: wal_level|replica 638 | DATA: wal_log_hints|off 639 | 640 | # 6.4 - Ensure 'SIGHUP' runtime parameters are configured correctly (Manual) 641 | In order to define server behavior and optimize server performance, the server's superuser has the privilege of setting these parameters which are found in the configuration files postgresql.conf and pg_hba.conf. Alternatively, those parameters found in postgresql.conf can also be changed using a server login session and executing the SQL command ALTER SYSTEM which writes its changes in the configuration file postgresql.auto.conf. All changes made on this level will affect the overall behavior of the server. These changes can be effected by editing the PostgreSQL configuration files and by either executing a server SIGHUP from the command line or, as superuser postgres, executing the SQL command select pg_reload_conf(). A denial of service is possible by the over-allocating of limited resources, such as RAM. Data can be corrupted by allowing damaged pages to load or by changing parameters to reinterpret values in an unexpected fashion, e.g. changing the time zone. Client messages can be altered in such a way as to interfere with the application logic. Logging can be altered and obfuscated inhibiting root cause analysis. 642 | 643 | DATA: name|setting 644 | DATA: archive_cleanup_command| 645 | DATA: archive_command|(disabled) 646 | DATA: archive_library| 647 | DATA: archive_timeout|0 648 | DATA: authentication_timeout|60 649 | DATA: autovacuum|on 650 | DATA: autovacuum_analyze_scale_factor|0.1 651 | DATA: autovacuum_analyze_threshold|50 652 | DATA: autovacuum_naptime|60 653 | DATA: autovacuum_vacuum_cost_delay|2 654 | DATA: autovacuum_vacuum_cost_limit|-1 655 | DATA: autovacuum_vacuum_insert_scale_factor|0.2 656 | DATA: autovacuum_vacuum_insert_threshold|1000 657 | DATA: autovacuum_vacuum_scale_factor|0.2 658 | DATA: autovacuum_vacuum_threshold|50 659 | DATA: autovacuum_work_mem|-1 660 | DATA: bgwriter_delay|200 661 | DATA: bgwriter_flush_after|64 662 | DATA: bgwriter_lru_maxpages|100 663 | DATA: bgwriter_lru_multiplier|2 664 | DATA: checkpoint_completion_target|0.9 665 | DATA: checkpoint_flush_after|32 666 | DATA: checkpoint_timeout|300 667 | DATA: checkpoint_warning|30 668 | DATA: credcheck.auth_delay_ms|0 669 | DATA: credcheck.reset_superuser|off 670 | DATA: db_user_namespace|off 671 | DATA: fsync|on 672 | DATA: full_page_writes|on 673 | DATA: hot_standby_feedback|off 674 | DATA: krb_caseins_users|off 675 | DATA: krb_server_keyfile|FILE:/etc/postgresql-common/krb5.keytab 676 | DATA: log_autovacuum_min_duration|600000 677 | DATA: log_checkpoints|on 678 | DATA: log_destination|csvlog 679 | DATA: log_directory|log 680 | DATA: log_file_mode|0600 681 | DATA: log_filename|postgresql-%a.log 682 | DATA: log_hostname|off 683 | DATA: log_line_prefix|%m [%p] %q%u@%d 684 | DATA: log_recovery_conflict_waits|off 685 | DATA: log_rotation_age|1440 686 | DATA: log_rotation_size|10240 687 | DATA: log_startup_progress_interval|10000 688 | DATA: log_timezone|Europe/Paris 689 | DATA: log_truncate_on_rotation|off 690 | DATA: max_pred_locks_per_page|2 691 | DATA: max_pred_locks_per_relation|-2 692 | DATA: max_slot_wal_keep_size|-1 693 | DATA: max_standby_archive_delay|30000 694 | DATA: max_standby_streaming_delay|30000 695 | DATA: max_sync_workers_per_subscription|2 696 | DATA: max_wal_size|1024 697 | DATA: min_wal_size|80 698 | DATA: pre_auth_delay|0 699 | DATA: primary_conninfo| 700 | DATA: primary_slot_name| 701 | DATA: promote_trigger_file| 702 | DATA: recovery_end_command| 703 | DATA: recovery_init_sync_method|fsync 704 | DATA: recovery_min_apply_delay|0 705 | DATA: recovery_prefetch|try 706 | DATA: remove_temp_files_after_crash|on 707 | DATA: restart_after_crash|on 708 | DATA: restore_command| 709 | DATA: ssl|on 710 | DATA: ssl_ca_file| 711 | DATA: ssl_cert_file|/etc/ssl/certs/ssl-cert-snakeoil.pem 712 | DATA: ssl_ciphers|HIGH:MEDIUM:+3DES:!aNULL 713 | DATA: ssl_crl_dir| 714 | DATA: ssl_crl_file| 715 | DATA: ssl_dh_params_file| 716 | DATA: ssl_ecdh_curve|prime256v1 717 | DATA: ssl_key_file|/etc/ssl/private/ssl-cert-snakeoil.key 718 | DATA: ssl_max_protocol_version| 719 | DATA: ssl_min_protocol_version|TLSv1.2 720 | DATA: ssl_passphrase_command| 721 | DATA: ssl_passphrase_command_supports_reload|off 722 | DATA: ssl_prefer_server_ciphers|on 723 | DATA: synchronous_standby_names| 724 | DATA: syslog_facility|local0 725 | DATA: syslog_ident|postgres 726 | DATA: syslog_sequence_numbers|on 727 | DATA: syslog_split_messages|on 728 | DATA: trace_recovery_messages|log 729 | DATA: vacuum_defer_cleanup_age|0 730 | DATA: wal_keep_size|0 731 | DATA: wal_receiver_create_temp_slot|off 732 | DATA: wal_receiver_status_interval|10 733 | DATA: wal_receiver_timeout|60000 734 | DATA: wal_retrieve_retry_interval|5000 735 | DATA: wal_sync_method|fdatasync 736 | DATA: wal_writer_delay|200 737 | DATA: wal_writer_flush_after|128 738 | 739 | # 6.5 - Ensure 'Superuser' runtime parameters are configured correctly (Manual) 740 | In order to improve and optimize server performance, the server's superuser has the privilege of setting these parameters which are found in the configuration file postgresql.conf. Alternatively, they can be changed in a PostgreSQL login session via the SQL command ALTER SYSTEM which writes its changes in the configuration file postgresql.auto.conf. All changes made on this level will affect the overall behavior of the server. These changes can only be affected by a server restart after the parameters have been altered in the configuration files. A denial of service is possible by the over-allocating of limited resources, such as RAM. Data can be corrupted by allowing damaged pages to load or by changing parameters to reinterpret values in an unexpected fashion, e.g. changing the time zone. Client messages can be altered in such a way as to interfere with the application logic. Logging can be altered and obfuscated inhibiting root cause analysis. 741 | 742 | DATA: name|setting 743 | DATA: allow_in_place_tablespaces|off 744 | DATA: allow_system_table_mods|off 745 | DATA: backtrace_functions| 746 | DATA: commit_delay|0 747 | DATA: compute_query_id|auto 748 | DATA: credcheck.encrypted_password_allowed|off 749 | DATA: credcheck.max_auth_failure|3 750 | DATA: credcheck.no_password_logging|on 751 | DATA: credcheck.password_contain| 752 | DATA: credcheck.password_contain_username|on 753 | DATA: credcheck.password_ignore_case|off 754 | DATA: credcheck.password_min_digit|0 755 | DATA: credcheck.password_min_length|1 756 | DATA: credcheck.password_min_lower|0 757 | DATA: credcheck.password_min_repeat|0 758 | DATA: credcheck.password_min_special|0 759 | DATA: credcheck.password_min_upper|0 760 | DATA: credcheck.password_not_contain| 761 | DATA: credcheck.password_reuse_history|2 762 | DATA: credcheck.password_reuse_interval|0 763 | DATA: credcheck.password_valid_max|0 764 | DATA: credcheck.password_valid_until|0 765 | DATA: credcheck.username_contain| 766 | DATA: credcheck.username_contain_password|on 767 | DATA: credcheck.username_ignore_case|off 768 | DATA: credcheck.username_min_digit|0 769 | DATA: credcheck.username_min_length|1 770 | DATA: credcheck.username_min_lower|0 771 | DATA: credcheck.username_min_repeat|0 772 | DATA: credcheck.username_min_special|0 773 | DATA: credcheck.username_min_upper|0 774 | DATA: credcheck.username_not_contain| 775 | DATA: credcheck.whitelist| 776 | DATA: deadlock_timeout|1000 777 | DATA: debug_discard_caches|0 778 | DATA: dynamic_library_path|$libdir 779 | DATA: extension_destdir| 780 | DATA: ignore_checksum_failure|off 781 | DATA: jit_dump_bitcode|off 782 | DATA: lc_messages|en_US.UTF-8 783 | DATA: lo_compat_privileges|off 784 | DATA: log_duration|off 785 | DATA: log_error_verbosity|default 786 | DATA: log_executor_stats|off 787 | DATA: log_lock_waits|on 788 | DATA: log_min_duration_sample|-1 789 | DATA: log_min_duration_statement|-1 790 | DATA: log_min_error_statement|error 791 | DATA: log_min_messages|warning 792 | DATA: log_parameter_max_length|-1 793 | DATA: log_parser_stats|off 794 | DATA: log_planner_stats|off 795 | DATA: log_replication_commands|off 796 | DATA: log_statement|none 797 | DATA: log_statement_sample_rate|1 798 | DATA: log_statement_stats|off 799 | DATA: log_temp_files|-1 800 | DATA: log_transaction_sample_rate|0.01 801 | DATA: max_stack_depth|2048 802 | DATA: session_preload_libraries| 803 | DATA: session_replication_role|origin 804 | DATA: temp_file_limit|-1 805 | DATA: track_activities|on 806 | DATA: track_counts|on 807 | DATA: track_functions|none 808 | DATA: track_io_timing|off 809 | DATA: track_wal_io_timing|off 810 | DATA: update_process_title|on 811 | DATA: wal_compression|off 812 | DATA: wal_consistency_checking| 813 | DATA: wal_init_zero|on 814 | DATA: wal_recycle|on 815 | DATA: zero_damaged_pages|off 816 | 817 | # 6.6 - Ensure 'User' runtime parameters are configured correctly (Manual) 818 | These PostgreSQL runtime parameters are managed at the user account (ROLE) level. In order to improve performance and optimize features, a ROLE has the privilege of setting numerous parameters in a transaction, session, or entity attribute. Any ROLE can alter any of these parameters. A denial of service is possible by the over-allocating of limited resources, such as RAM. Changing VACUUM parameters can force a server shutdown which is standard procedure preventing data corruption from transaction ID wraparound. Data can be corrupted by changing parameters to reinterpret values in an unexpected fashion, e.g. changing the time zone. Logging can be altered and obfuscated to inhibit root cause analysis. 819 | 820 | DATA: name|setting 821 | DATA: application_name|psql 822 | DATA: array_nulls|on 823 | DATA: backend_flush_after|0 824 | DATA: backslash_quote|safe_encoding 825 | DATA: bytea_output|hex 826 | DATA: check_function_bodies|on 827 | DATA: client_connection_check_interval|0 828 | DATA: client_encoding|UTF8 829 | DATA: client_min_messages|notice 830 | DATA: commit_siblings|5 831 | DATA: constraint_exclusion|partition 832 | DATA: cpu_index_tuple_cost|0.005 833 | DATA: cpu_operator_cost|0.0025 834 | DATA: cpu_tuple_cost|0.01 835 | DATA: cursor_tuple_fraction|0.1 836 | DATA: DateStyle|ISO, DMY 837 | DATA: debug_pretty_print|on 838 | DATA: debug_print_parse|off 839 | DATA: debug_print_plan|off 840 | DATA: debug_print_rewritten|off 841 | DATA: default_statistics_target|100 842 | DATA: default_table_access_method|heap 843 | DATA: default_tablespace| 844 | DATA: default_text_search_config|pg_catalog.french 845 | DATA: default_toast_compression|pglz 846 | DATA: default_transaction_deferrable|off 847 | DATA: default_transaction_isolation|read committed 848 | DATA: default_transaction_read_only|off 849 | DATA: effective_cache_size|524288 850 | DATA: effective_io_concurrency|1 851 | DATA: enable_async_append|on 852 | DATA: enable_bitmapscan|on 853 | DATA: enable_gathermerge|on 854 | DATA: enable_hashagg|on 855 | DATA: enable_hashjoin|on 856 | DATA: enable_incremental_sort|on 857 | DATA: enable_indexonlyscan|on 858 | DATA: enable_indexscan|on 859 | DATA: enable_material|on 860 | DATA: enable_memoize|on 861 | DATA: enable_mergejoin|on 862 | DATA: enable_nestloop|on 863 | DATA: enable_parallel_append|on 864 | DATA: enable_parallel_hash|on 865 | DATA: enable_partition_pruning|on 866 | DATA: enable_partitionwise_aggregate|off 867 | DATA: enable_partitionwise_join|off 868 | DATA: enable_seqscan|on 869 | DATA: enable_sort|on 870 | DATA: enable_tidscan|on 871 | DATA: escape_string_warning|on 872 | DATA: exit_on_error|off 873 | DATA: extra_float_digits|1 874 | DATA: force_parallel_mode|off 875 | DATA: from_collapse_limit|8 876 | DATA: geqo|on 877 | DATA: geqo_effort|5 878 | DATA: geqo_generations|0 879 | DATA: geqo_pool_size|0 880 | DATA: geqo_seed|0 881 | DATA: geqo_selection_bias|2 882 | DATA: geqo_threshold|12 883 | DATA: gin_fuzzy_search_limit|0 884 | DATA: gin_pending_list_limit|4096 885 | DATA: hash_mem_multiplier|2 886 | DATA: idle_in_transaction_session_timeout|0 887 | DATA: idle_session_timeout|0 888 | DATA: IntervalStyle|postgres 889 | DATA: jit|on 890 | DATA: jit_above_cost|100000 891 | DATA: jit_expressions|on 892 | DATA: jit_inline_above_cost|500000 893 | DATA: jit_optimize_above_cost|500000 894 | DATA: jit_tuple_deforming|on 895 | DATA: join_collapse_limit|8 896 | DATA: lc_monetary|fr_FR.UTF-8 897 | DATA: lc_numeric|fr_FR.UTF-8 898 | DATA: lc_time|fr_FR.UTF-8 899 | DATA: local_preload_libraries| 900 | DATA: lock_timeout|0 901 | DATA: logical_decoding_work_mem|65536 902 | DATA: log_parameter_max_length_on_error|0 903 | DATA: maintenance_io_concurrency|10 904 | DATA: maintenance_work_mem|65536 905 | DATA: max_parallel_maintenance_workers|2 906 | DATA: max_parallel_workers|8 907 | DATA: max_parallel_workers_per_gather|2 908 | DATA: min_parallel_index_scan_size|64 909 | DATA: min_parallel_table_scan_size|1024 910 | DATA: parallel_leader_participation|on 911 | DATA: parallel_setup_cost|1000 912 | DATA: parallel_tuple_cost|0.1 913 | DATA: password_encryption|scram-sha-256 914 | DATA: plan_cache_mode|auto 915 | DATA: quote_all_identifiers|off 916 | DATA: random_page_cost|4 917 | DATA: recursive_worktable_factor|10 918 | DATA: row_security|on 919 | DATA: search_path|"$user", public 920 | DATA: seq_page_cost|1 921 | DATA: standard_conforming_strings|on 922 | DATA: statement_timeout|0 923 | DATA: stats_fetch_consistency|cache 924 | DATA: synchronize_seqscans|on 925 | DATA: synchronous_commit|on 926 | DATA: tcp_keepalives_count|9 927 | DATA: tcp_keepalives_idle|7200 928 | DATA: tcp_keepalives_interval|75 929 | DATA: tcp_user_timeout|0 930 | DATA: temp_buffers|1024 931 | DATA: temp_tablespaces| 932 | DATA: TimeZone|Europe/Paris 933 | DATA: timezone_abbreviations|Default 934 | DATA: trace_notify|off 935 | DATA: trace_sort|off 936 | DATA: transaction_deferrable|off 937 | DATA: transaction_isolation|read committed 938 | DATA: transaction_read_only|off 939 | DATA: transform_null_equals|off 940 | DATA: vacuum_cost_delay|0 941 | DATA: vacuum_cost_limit|200 942 | DATA: vacuum_cost_page_dirty|20 943 | DATA: vacuum_cost_page_hit|1 944 | DATA: vacuum_cost_page_miss|2 945 | DATA: vacuum_failsafe_age|1600000000 946 | DATA: vacuum_freeze_min_age|50000000 947 | DATA: vacuum_freeze_table_age|150000000 948 | DATA: vacuum_multixact_failsafe_age|1600000000 949 | DATA: vacuum_multixact_freeze_min_age|5000000 950 | DATA: vacuum_multixact_freeze_table_age|150000000 951 | DATA: wal_sender_timeout|60000 952 | DATA: wal_skip_threshold|2048 953 | DATA: work_mem|4096 954 | DATA: xmlbinary|base64 955 | DATA: xmloption|content 956 | 957 | # 6.7 - Ensure FIPS 140-2 OpenSSL cryptography is used 958 | Install, configure, and use OpenSSL on a platform that has a NIST certified FIPS 140-2 installation of OpenSSL. This provides PostgreSQL instances the ability to generate and validate cryptographic hashes to protect unclassified information requiring confidentiality and cryptographic protection, in accordance with the data owner's requirements. 959 | 960 | CRITICAL: Installation of FIPS modules is not completed. 961 | 962 | INFO: See "switching the system to fips mode" to enable FIPS mode 963 | 964 | DATA: OpenSSL 3.0.2 15 Mar 2022 (Library: OpenSSL 3.0.2 15 Mar 2022) 965 | 966 | # 6.8 - Ensure TLS is enabled and configured correctly 967 | If TLS is not enabled and configured correctly, this increases the risk of data being compromised in transit. A self-signed certificate can be used for testing, but a certificate signed by a certificate authority (CA) (either one of the global CAs or a local one) should be used in production so that clients can verify the server's identity. If all the database clients are local to the organization, using a local CA is recommended. To ultimately enable and enforce TLS authentication for the server, appropriate "hostssl" records must be added to the pg_hba.conf file. 968 | 969 | WARNING: Setting 'ssl_min_protocol_version' should be TLS v1.3 or newer. 970 | 971 | WARNING: The SSL certificate should have a passphrase and setting 'ssl_passphrase_command' should be set. 972 | 973 | CRITICAL: To enforce TLS authentication for the server, appropriate "hostssl" or "hostgssenc" records must be added to the pg_hba.conf file and "host" connections rejected. 974 | 975 | # 6.9 - Ensure a cryptographic extension is installed 976 | PostgreSQL instances handling data that requires "data at rest" protections must employ cryptographic mechanisms to prevent unauthorized disclosure and modification of the information at rest. These cryptographic mechanisms may be native to PostgreSQL or implemented via additional software or operating system/file system settings, as appropriate to the situation. 977 | 978 | SUCCESS: Test passed 979 | 980 | # 6.10 - Ensure a data anonymization extension is installed 981 | To mask or replace information that could permit to identify a person or to prevent exposing sensitive data, a data anomymization extension should be installed on the PostgreSQL cluster. Check that extensions pg_anonymize or anon are set in 'session_preload_libraries' (*). 982 | 983 | WARNING: Extensions pg_anonymize or anon are not installed. 984 | 985 | #-------------------------------------------------------------------------------- 986 | # 7 - Replication 987 | #-------------------------------------------------------------------------------- 988 | # 7.1 - Ensure a replication-only user is created and used for streaming replication 989 | As it is not necessary to be a superuser to initiate a replication connection, it is proper to create an account specifically for replication. This allows further "locking down" the uses of the superuser account and follows the general principle of using the least privileges necessary. 990 | 991 | WARNING: A replication-only user should be created. 992 | 993 | # 7.2 - Ensure logging of replication commands is configured 994 | A successful replication connection allows for a complete copy of the data stored within the data cluster to be offloaded to another, potentially insecure, host. As such, it is advisable to log all replication commands that are executed in your database cluster to ensure the data is not off-loaded to an unexpected/undesired location. 995 | 996 | WARNING: Setting 'log_replication_commands' should be enabled. 997 | 998 | # 7.3 - Ensure base backups are configured and functional 999 | A 'base backup' is a copy of the PRIMARY host's data cluster (PGDATA) and is used to create STANDBY hosts and for Point In Time Recovery (PITR) mechanisms. Base backups should be copied across networks in a secure manner using an encrypted transport mechanism. The PostgreSQL CLI pg_basebackup can be used, however, TLS encryption should be enabled on the server as per section 6.8 of this benchmark. 1000 | 1001 | # 7.4 - Ensure WAL archiving is configured and functional 1002 | Write Ahead Log (WAL) Archiving, or Log Shipping, is the process of sending transaction log files from the PRIMARY host either to one or more STANDBY hosts or to be archived on a remote storage device for later use, e.g. PITR. There are several utilities that can copy WALs including, but not limited to, cp, scp, sftp, and rynsc. Basically, the server follows a set of runtime parameters which define when the WAL should be copied using one of the aforementioned utilities. 1003 | 1004 | CRITICAL: WAL archiving is not activated. Setting 'archive_mode' must be enabled. 1005 | 1006 | # 7.5 - Ensure streaming replication parameters are configured correctly 1007 | Streaming replication from a PRIMARY host transmits DDL, DML, passwords, and other potentially sensitive activities and data. These connections should be protected with Secure Sockets Layer (SSL). Verify on STANDBY that primary_conninfo contains 'sslmode=require sslcompression=1' 1008 | 1009 | CRITICAL: Setting 'primary_conninfo' must enforce TLS encryption of the replication (sslmode=required). 1010 | 1011 | #-------------------------------------------------------------------------------- 1012 | # 8 - Special Configuration Considerations 1013 | #-------------------------------------------------------------------------------- 1014 | The recommendations proposed here try to address some of the less common use cases which may warrant additional configuration guidance/consideration. 1015 | 1016 | # 8.1 - Ensure PostgreSQL subdirectory locations are outside the data cluster 1017 | This report is part of chapter "1.3 Ensure Data Cluster Initialized Successfully". 1018 | 1019 | # 8.2 - Ensure the backup and restore tool, 'pgBackRest', is installed and configured 1020 | The native PostgreSQL backup facility pg_dump provides adequate logical backup operations but does not provide for Point In Time Recovery (PITR). The PostgreSQL facility pg_basebackup performs a physical backup of the database files and does provide for PITR, but it is constrained by single threading. Both of these methodologies are standard in the PostgreSQL ecosystem and appropriate for particular backup/recovery needs. pgBackRest offers another option with much more robust features and flexibility. 1021 | 1022 | SUCCESS: Test passed 1023 | 1024 | # 8.3 - Ensure miscellaneous configuration settings are correct (Manual) 1025 | This recommendation covers non-regular, special files, and dynamic libraries. PostgreSQL permits local logins via the UNIX DOMAIN SOCKET and, for the most part, anyone with a legitimate Unix login account can make the attempt. Limiting PostgreSQL login attempts can be made by relocating the UNIX DOMAIN SOCKET to a subdirectory with restricted permissions. The creation and implementation of user-defined dynamic libraries is an extraordinary powerful capability. In the hands of an experienced DBA/programmer, it can significantly enhance the power and flexibility of the RDBMS; but new and unexpected behavior can also be assigned to the RDBMS, resulting in a very dangerous environment in what should otherwise be trusted. 1026 | 1027 | DATA: name|setting 1028 | DATA: dynamic_library_path|$libdir 1029 | DATA: external_pid_file|/var/run/postgresql/15-main.pid 1030 | DATA: local_preload_libraries| 1031 | DATA: session_preload_libraries| 1032 | DATA: shared_preload_libraries|credcheck 1033 | DATA: unix_socket_directories|/var/run/postgresql 1034 | 1035 | 1036 | (*) Check not part of the CIS Benchmark 1037 | --------------------------------------------------------------------------------