You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/source/pages/developers_guide/index.rst
+157-1Lines changed: 157 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -1,4 +1,4 @@
1
-
.. Copyright (c) 2023 - 2023, Oracle and/or its affiliates. All rights reserved.
1
+
.. Copyright (c) 2023 - 2024, Oracle and/or its affiliates. All rights reserved.
2
2
.. Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.
3
3
4
4
=========================
@@ -11,6 +11,162 @@ To follow the project's code style, see the :doc:`Macaron Style Guide </pages/de
11
11
12
12
For API reference, see the :doc:`API Reference </pages/developers_guide/apidoc/index>` page.
13
13
14
+
-------------------
15
+
Writing a New Check
16
+
-------------------
17
+
18
+
As a contributor to Macaron, it is very likely to need to write a new check or modify an existing one at some point. In this
19
+
section, we will understand how Macaron checks work and what we need to do to develop one.
20
+
21
+
+++++++++++++++++
22
+
High-level Design
23
+
+++++++++++++++++
24
+
25
+
Before jumping into coding, it is useful to understand how Macaron as a framework works. Macaron is an extensible
26
+
framework designed to make writing new supply chain security analyses easy. It provides an interface
27
+
that you can leverage to access existing models and abstractions instead of implementing everything from scratch. For
28
+
instance, many security checks require to traverse through the code in GitHub Actions configurations. Normally,
29
+
you would need to find the right repository and commit, clone it, find the workflows, and parse them. With Macaron,
30
+
you don't need to do any of that and can simply write your security check by using the parsed shell scripts that are
31
+
triggered in the CI.
32
+
33
+
Another important aspect of our design is that all the check results are automatically mapped and stored in a local database.
34
+
By performing this mapping, we make it possible to enforce flexible policies on the results of the checks. While storing
35
+
the check results to the database happens automatically by Macaron's backend, the developer needs to add a brief specification
36
+
to make that possible as we will see later.
37
+
38
+
+++++++++++++++++++
39
+
The Check Interface
40
+
+++++++++++++++++++
41
+
42
+
Each check needs to be implemented as a Python class in a Python module under ``src/macaron/slsa_analyzer/checks``.
43
+
A check class should subclass the ``BaseCheck`` class in :ref:`base_check module <pages/developers_guide/apidoc/macaron\.slsa_analyzer\.checks:macaron.slsa\\_analyzer.checks.base\\_check module>`.
44
+
45
+
You need to set the name, description, and other details of your new check in the ``__init__`` method. After implementing
46
+
the initializer, you need to implement the ``run_check`` abstract method. This method provides the context object
47
+
:ref:`AnalyzeContext <pages/developers_guide/apidoc/macaron\.slsa_analyzer:macaron.slsa\\_analyzer.analyze\\_context module>`, which contains various
48
+
intermediate representations and models. The ``dynamic_data`` property would be particularly useful as it contains
49
+
data about the CI service, artifact registry, and build tool used for building the software component.
50
+
51
+
``component`` is another useful attribute in the :ref:`AnalyzeContext <pages/developers_guide/apidoc/macaron\.slsa_analyzer:macaron.slsa\\_analyzer.analyze\\_context module>` object
52
+
that you should know about. This attribute contains the information about a software component, such
53
+
as it's corresponding ``repository`` and ``dependencies``. Note that ``component`` will also be stored into the database and its attributes
54
+
such as ``repository`` are established as database relationships. You can see the existing tables and their
55
+
relationships in our :ref:`data model <pages/developers_guide/apidoc/macaron.database:macaron.database.table\\_definitions module>`.
56
+
57
+
Once you implement the logic of your check in the ``run_check`` method, you need to add a class to help
58
+
Macaron handle your check's output:
59
+
60
+
* Add a class that subclasses ``CheckFacts`` to map your outputs to a table in the database. The class name should follow the ``<MyCheck>Facts`` pattern.
61
+
* Specify the table name in the ``__tablename__ = "_my_check"`` class variable. Note that the table name should start with ``_`` and it should not have been used by other checks.
62
+
* Add the ``id`` column as the primary key where the foreign key is ``_check_facts.id``.
63
+
* Add columns for the check outputs that you would like to store into the database. If a column needs to appear as a justification in the HTML/JSON report, pass ``info={"justification": JustificationType.<TEXT or HREF>}`` to the column mapper.
64
+
* Add ``__mapper_args__`` class variable and set ``"polymorphic_identity"`` key to the table name.
65
+
66
+
Next, you need to create a ``result_tables`` list and append check facts as part of the ``run_check`` implementation.
67
+
You should also specify a :ref:`Confidence <pages/developers_guide/apidoc/macaron\.slsa_analyzer\.checks:macaron.slsa\\_analyzer.checks.check\\_result module>`
68
+
score choosing one of the ``Confidence`` enum values, e.g., ``Confidence.HIGH`` and pass it via keyword
69
+
argument ``confidence``. You should choose a suitable confidence score based on the accuracy
This list as well as the check result status should be stored in a :ref:`CheckResultData <pages/developers_guide/apidoc/macaron\.slsa_analyzer\.checks:macaron.slsa\\_analyzer.checks.check\\_result module>`
77
+
object and returned by ``run_check``.
78
+
79
+
Finally, you need to register your check by adding it to the :ref:`registry module <pages/developers_guide/apidoc/macaron\.slsa_analyzer:macaron.slsa\\_analyzer.registry module>`:
80
+
81
+
.. code-block:: python
82
+
83
+
registry.register(MyCheck())
84
+
85
+
And of course, make sure to add tests for you check by adding a module under ``tests/slsa_analyzer/checks/``.
86
+
87
+
+++++++
88
+
Example
89
+
+++++++
90
+
91
+
In this example, we show how to add a check determine if a software component has a source-code repository.
92
+
Feel free to explore other existing checks under ``src/macaron/slsa_analyzer/checks`` for more examples.
93
+
94
+
1. First create a module called ``repo_check.py`` under ``src/macaron/slsa_analyzer/checks``.
95
+
96
+
2. Add a class and specify the columns that you want to store for the check outputs to the database.
97
+
98
+
.. code-block:: python
99
+
100
+
# Add this line at the top of the file to create the logger object if you plan to use it.
The checks defined in this directory are automatically loaded during the startup of Macaron and used during the analysis. This `README.md` shows how a Check can be created.
3
+
The checks defined in this directory are automatically loaded during the startup of Macaron and used during the analysis. For detailed instructions to write a new check, see our [website](https://oracle.github.io/macaron/pages/developers_guide/index.html).
4
4
5
-
## Base Check
6
-
The `BaseCheck` class (located at [base_check.py](./base_check.py)) is the abstract class to be inherited by other concrete checks.
7
-
Please see [base_check.py](./base_check.py) for the attributes of a `BaseCheck` instance.
8
5
9
-
## Writing a Macaron Check
10
-
These are the steps for creating a Check in Macaron:
11
-
1. Create a module with the name `<name>_check.py`. Note that Macaron **only** loads check modules that have this name format.
12
-
2. Create a class that inherits `BaseCheck` and initiates the attributes of a `BaseCheck` instance.
13
-
3. Register the newly created Check class to the Registry ([registry.py](../registry.py)). This will make the Check available to Macaron. For example:
14
-
```python
15
-
from macaron.slsa_analyzer.registry import registry
16
-
17
-
# Check class is defined here
18
-
# class ExampleCheck(BaseCheck):
19
-
# ...
20
-
21
-
registry.register(ExampleCheck())
22
-
```
23
-
4. Add an ORM mapped class for the check facts so that the policy engine can reason about the properties. To provide the mapped class, all you need to do is to add a class that inherits from `CheckFacts` class and add the following attributes (rename the `MyCheckFacts` check name and `__tablename__` as appropriate).
24
-
25
-
```python
26
-
classMyCheckFacts(CheckFacts):
27
-
"""The ORM mapping for justifications in my check."""
0 commit comments