Principal Mapper (PMapper) is a script and library for identifying risks in the configuration of AWS Identity and Access Management (IAM) in an AWS account.
PMapper allows users to identify which IAM users and roles have access to certain actions and resources in an AWS account. This is important for ensuring that sensitive resources, such as S3 objects with PII, are isolated.
PMapper creates a graph of an AWS account's IAM users and roles (principals). This graph, composed of nodes and edges, represents the different ways that one principal could access another. When running a query to determine if a principal has access to a certain action/resource, it also checks if the user or role could access other users or roles that have access to that action/resource. This catches scenarios such as when a user doesn't have direct access to an S3 object, but could launch an EC2 instance that has access to the S3 object.
Principal Mapper is built using the botocore
library and Python 3.5+. Python 2 is not supported. Principal Mapper
also requires pydot
(available on pip
), and graphviz
(available on Windows, macOS, and Linux from
https://graphviz.org/ ).
pip install principalmapper
Clone the repository:
git clone git@github.com:nccgroup/PMapper.git
Then install with Pip:
cd PMapper
pip install .
To start, create a graph for an AWS account:
pmapper graph --create
This stores information locally on disk about all the IAM users, roles, groups, and policies in the account. Accounts that are already graphed can be found using:
pmapper graph --list
The account IDs that are printed can be used in other pmapper
subcommands via the --account
parameter.
After creating a graph, write queries to learn more about which users and roles can access certain actions or resources.
pmapper argquery --action s3:GetObject --resource arn:aws:s3:::bucket/path/to/object
argquery
takes the elements to check for (principal, action, resource, conditions) as arguments of the pmapper
command. When --principal
is not specified, it runs the query for all IAM users and roles in the account. When
--resource
is not specified, it defaults to the wildcard (*
).
query
parses a more human-readable input string into a query and returns the results.
pmapper query "who can do s3:GetObject with arn:aws:s3:::bucket/path/to/object"
There are two special queries, presets, available:
privesc
: Identify privilege escalation risks.connected
: Identify which principals can access other principals.
These presets are accessible from query
and argquery
. See the following examples:
# Determine if PowerUser can escalate privileges
pmapper query "preset privesc user/PowerUser"
pmapper argquery --principal user/PowerUser --preset privesc
# Find all principals that can escalate privileges
pmapper query "preset privesc *"
pmapper argquery --principal '*' --preset privesc
# Find all principals that PowerUser can access
pmapper query "preset connected user/PowerUser *"
pmapper argquery --principal user/PowerUser --resource '*' --preset connected
# Find all principals that can access PowerUser
pmapper query "preset connected * user/PowerUser"
pmapper argquery --principal '*' --resource user/PowerUser --preset connected
The Read-Evaluate-Print-Loop (REPL) is a program for running several queries at once. The REPL has four commands:
query
: Executes human-readable queries.argquery
: Executes queries with a set of parameters.help
: Prints out information on how to use the REPL.exit
: Exits the REPL (Ctrl+C should also work).
When the REPL is launched, it loads the data for a single graph and executes all queries against that graph. You can launch the repl like so:
pmapper repl
Interacting with the REPL is very similar to running multiple queries from the command-line:
repl> query "who can do s3:GetObject with *"
...
repl> argquery --principal "*" --preset privesc
PMapper includes a visualization feature, which draws a specified graph. This graph highlights principals with
administrative privileges in blue, and principals that can escalate privileges in red. It supports SVG, PNG, and DOT
file outputs. It uses graphviz
in order to create the image. It can be used like so:
pmapper visualize --filetype png
PMapper provides analysis to identify risks with the configuration in an account. It provides details on the risk, what
impact it could have on the account, which principals are affected, and a recommendation on how to mitigate the risk.
The outputs from analysis
can be in text or JSON format, and can be created with the following command:
pmapper analysis --output-type text
PMapper grabs credentials in the following order:
- The
--profile
argument, when specified, is checked first. If specified, PMapper grabs credentials from botocore for that profile name. - PMapper uses the
get_session()
function frombotocore.session
, which should grab credentials from the environment variables/metadata service/default profile.
For querying, REPL, visualization, and analysis, you can specify the --account
argument with the 12-digit ID of the
account to examine. This cannot be specified along with --profile
. It directs PMapper to use that account
for the command, rather that deriving the account from credentials.
Principal Mapper includes a library that can be used instead of the command-line interface. All functions and methods have type-hints and a small amount of documentation for reference. See example_script.py for an example.
Future major-version revisions (e.g. 1.X.X -> 2.0.0) of Principal Mapper may alter/remove functions/classes/methods. Minor-version revisions (e.g. 1.1.X -> 1.2.0) will not remove existing functions/classes/methods but may add new ones or alter their behaviors.
Exception: All instances of the debug
parameter in all of the functions in this library, which is used by the
dprint
function, will eventually be removed as the logging bits are improved. Just keep the debug
parameter out
of your production code. dprint
is gonna be replaced too.
principalmapper.common
-
Classes
Graph
,Node
,Edge
,Group
, andPolicy
. These can be imported straight throughprincipalmapper.common
with a single statement:from principalmapper.common import Graph, Node, Edge
-
principalmapper.graphing.graph_actions
- function
get_existing_graph
: grabs a Graph object from disk, based on current botocore session or account ID, from a standard location on-disk. - function
get_graph_from_disk
: grabs a Graph object from disk, user-specified directory.
- function
principalmapper.graphing.gathering
- function
create_graph
: generates Graph objects using a botocore Session object.
- function
principalmapper.graphing.edge_identification
- variable
checker_map
: a dictionary, the keys of which are services that this version of Principal Mapper can get edge data from. This should always be updated with all services that are supported, andchecker_map.keys()
can always be passed tocreate_graph
without error.
- variable
principalmapper.querying.query_interface
- function
search_authorization_for
: performs an expansive search to determine if a principal can make a given AWS API call, or if the principal can access another that does have permission. ReturnsQueryResult
objects (defined inprincipalmapper.querying.query_result
) with information on if the principal is authorized, or if it has to chain through other principals with authorization. - function
local_check_authorization
: determines if a principal can make a given AWS API call, but DOES NOT perform the expansive search ofsearch_authorization_for
. - function
local_check_authorization_handling_mfa
: determines if a principal can make a given AWS API call, DOES NOT perform the expansive search ofsearch_authorization_for
, but DOES manipulate condition keys to test if the AWS API call can be made with or without MFA. Note that you can achieve the same effect by callinglocal_check_authorization
and setting the multi-factor auth conditions.
- function
principalmapper.visualizing.graph_writer
:- function
handle_request
: creates an image file (PNG/SVG) or graph file (DOT)
- function
principalmapper.analysis.find_risks
:- function
gen_findings_and_print
: dumps findings in markdown(text)/JSON format to stdout. Wraps aroundgen_report
, which can be used instead for custom formatting by pullingReport
andFinding
objects.
- function
principalmapper.analysis.report
:- class
Report
: a simple object containing metadata about generated findings (account ID, date, version of PMapper).
- class
principalmapper.analysis.finding
:- class
Finding
: a simple object containing data about a risk to the AWS account.
- class
principalmapper.util.arns
:- functions prefixed with
get_
: extract a specific chunk of an ARN, like the account ID or region.
- functions prefixed with
principalmapper.util.botocore_tools
:- function
get_session
: get a botocore Session object based on optional profile parameter.
- function
principalmapper.util.storage
:- function
get_storage_root
: returns a path on disk that Principal Mapper will use for storing Graph data by default. Output depends on OS.
- function
Copyright (c) NCC Group and Erik Steringer 2019. This file is part of Principal Mapper.
Principal Mapper is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Principal Mapper is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Principal Mapper. If not, see <https://www.gnu.org/licenses/>.