Skip to content

BBOTCore

This is the first thing that loads when you import BBOT.

Unlike a Preset, BBOTCore holds only the config, not scan-specific stuff like targets, flags, modules, etc.

Its main jobs are:

  • set up logging
  • keep separation between the default and custom config (this allows presets to only display the config options that have changed)
  • allow for easy merging of configs
  • load quickly
Source code in bbot/core/core.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
class BBOTCore:
    """
    This is the first thing that loads when you import BBOT.

    Unlike a Preset, BBOTCore holds only the config, not scan-specific stuff like targets, flags, modules, etc.

    Its main jobs are:

    - set up logging
    - keep separation between the `default` and `custom` config (this allows presets to only display the config options that have changed)
    - allow for easy merging of configs
    - load quickly
    """

    # used for filtering out sensitive config values
    secrets_strings = ["api_key", "username", "password", "token", "secret", "_id"]
    # don't filter/remove entries under this key
    secrets_exclude_keys = ["modules"]

    def __init__(self):
        self._logger = None
        self._files_config = None

        self._config = None
        self._custom_config = None

        # bare minimum == logging
        self.logger
        self.log = logging.getLogger("bbot.core")

        self._prep_multiprocessing()

    def _prep_multiprocessing(self):
        import multiprocessing
        from .helpers.process import BBOTProcess

        if SHARED_INTERPRETER_STATE.is_main_process:
            # if this is the main bbot process, set the logger and queue for the first time
            from functools import partialmethod

            BBOTProcess.__init__ = partialmethod(
                BBOTProcess.__init__, log_level=self.logger.log_level, log_queue=self.logger.queue
            )

        # this makes our process class the default for process pools, etc.
        mp_context = multiprocessing.get_context("spawn")
        mp_context.Process = BBOTProcess

    @property
    def home(self):
        return Path(self.config["home"]).expanduser().resolve()

    @property
    def cache_dir(self):
        return self.home / "cache"

    @property
    def tools_dir(self):
        return self.home / "tools"

    @property
    def temp_dir(self):
        return self.home / "temp"

    @property
    def lib_dir(self):
        return self.home / "lib"

    @property
    def scans_dir(self):
        return self.home / "scans"

    @property
    def config(self):
        """
        .config is just .default_config + .custom_config merged together

        any new values should be added to custom_config.
        """
        if self._config is None:
            self._config = OmegaConf.merge(self.default_config, self.custom_config)
            # set read-only flag (change .custom_config instead)
            OmegaConf.set_readonly(self._config, True)
        return self._config

    @property
    def default_config(self):
        """
        The default BBOT config (from `defaults.yml`). Read-only.
        """
        global DEFAULT_CONFIG
        if DEFAULT_CONFIG is None:
            self.default_config = self.files_config.get_default_config()
            # ensure bbot home dir
            if "home" not in self.default_config:
                self.default_config["home"] = "~/.bbot"
        return DEFAULT_CONFIG

    @default_config.setter
    def default_config(self, value):
        # we temporarily clear out the config so it can be refreshed if/when default_config changes
        global DEFAULT_CONFIG
        self._config = None
        DEFAULT_CONFIG = value
        # set read-only flag (change .custom_config instead)
        OmegaConf.set_readonly(DEFAULT_CONFIG, True)

    @property
    def custom_config(self):
        """
        Custom BBOT config (from `~/.config/bbot/bbot.yml`)
        """
        # we temporarily clear out the config so it can be refreshed if/when custom_config changes
        self._config = None
        if self._custom_config is None:
            self.custom_config = self.files_config.get_custom_config()
        return self._custom_config

    @custom_config.setter
    def custom_config(self, value):
        # we temporarily clear out the config so it can be refreshed if/when custom_config changes
        self._config = None
        # ensure the modules key is always a dictionary
        modules_entry = value.get("modules", None)
        if modules_entry is not None and not OmegaConf.is_dict(modules_entry):
            value["modules"] = {}
        self._custom_config = value

    def no_secrets_config(self, config):
        from .helpers.misc import clean_dict

        with suppress(ValueError):
            config = OmegaConf.to_object(config)

        return clean_dict(
            config,
            *self.secrets_strings,
            fuzzy=True,
            exclude_keys=self.secrets_exclude_keys,
        )

    def secrets_only_config(self, config):
        from .helpers.misc import filter_dict

        with suppress(ValueError):
            config = OmegaConf.to_object(config)

        return filter_dict(
            config,
            *self.secrets_strings,
            fuzzy=True,
            exclude_keys=self.secrets_exclude_keys,
        )

    def merge_custom(self, config):
        """
        Merge a config into the custom config.
        """
        self.custom_config = OmegaConf.merge(self.custom_config, OmegaConf.create(config))

    def merge_default(self, config):
        """
        Merge a config into the default config.
        """
        self.default_config = OmegaConf.merge(self.default_config, OmegaConf.create(config))

    def copy(self):
        """
        Return a semi-shallow copy of self. (`custom_config` is copied, but `default_config` stays the same)
        """
        core_copy = copy(self)
        core_copy._custom_config = self._custom_config.copy()
        return core_copy

    @property
    def files_config(self):
        """
        Get the configs from `bbot.yml` and `defaults.yml`
        """
        if self._files_config is None:
            from .config import files

            self.files = files
            self._files_config = files.BBOTConfigFiles(self)
        return self._files_config

    def create_process(self, *args, **kwargs):
        if os.environ.get("BBOT_TESTING", "") == "True":
            process = self.create_thread(*args, **kwargs)
        else:
            if SHARED_INTERPRETER_STATE.is_scan_process:
                from .helpers.process import BBOTProcess

                process = BBOTProcess(*args, **kwargs)
            else:
                import multiprocessing

                raise BBOTError(f"Tried to start server from process {multiprocessing.current_process().name}")
        process.daemon = True
        return process

    def create_thread(self, *args, **kwargs):
        from .helpers.process import BBOTThread

        return BBOTThread(*args, **kwargs)

    @property
    def logger(self):
        self.config
        if self._logger is None:
            from .config.logger import BBOTLogger

            self._logger = BBOTLogger(self)
        return self._logger

config property

config

.config is just .default_config + .custom_config merged together

any new values should be added to custom_config.

custom_config property writable

custom_config

Custom BBOT config (from ~/.config/bbot/bbot.yml)

default_config property writable

default_config

The default BBOT config (from defaults.yml). Read-only.

files_config property

files_config

Get the configs from bbot.yml and defaults.yml

copy

copy()

Return a semi-shallow copy of self. (custom_config is copied, but default_config stays the same)

Source code in bbot/core/core.py
181
182
183
184
185
186
187
def copy(self):
    """
    Return a semi-shallow copy of self. (`custom_config` is copied, but `default_config` stays the same)
    """
    core_copy = copy(self)
    core_copy._custom_config = self._custom_config.copy()
    return core_copy

merge_custom

merge_custom(config)

Merge a config into the custom config.

Source code in bbot/core/core.py
169
170
171
172
173
def merge_custom(self, config):
    """
    Merge a config into the custom config.
    """
    self.custom_config = OmegaConf.merge(self.custom_config, OmegaConf.create(config))

merge_default

merge_default(config)

Merge a config into the default config.

Source code in bbot/core/core.py
175
176
177
178
179
def merge_default(self, config):
    """
    Merge a config into the default config.
    """
    self.default_config = OmegaConf.merge(self.default_config, OmegaConf.create(config))