github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/CONTRIBUTING.md (about)

     1  
     2  # Contributing
     3  
     4  Welcome, and thank you for considering contributing to defsec!
     5  
     6  The following guide gives an overview of the project and some directions on how to make common types of contribution. If something is missing or you get stuck, please [jump on Slack](https://slack.khulnasoft.com/) or [start a discussion](https://github.com/khulnasoft-lab/defsec/discussions/new) and we'll do our best to help.
     7  
     8  ## Project Overview
     9  
    10  _defsec_ is a library for defining security rules and policies in code, and the tools to apply those rules/cloud/policies to a variety of sources. The general architecture and project layout are defined in [ARCHITECTURE.md](ARCHITECTURE.md) - this is a great place to start exploring.
    11  
    12  _defsec_ is also the misconfiguration/IaC/Cloud scanning engine for Vul. Vul uses defsec internally as a library to perform various scans. 
    13  
    14  ## Guides
    15  
    16  The following are guides for contributing to the project in specific ways. If you're not sure where to start, these are a good place to look. If you need some tips on getting started with contributing to open source in general, check out this useful [GitHub contribution guide](https://docs.github.com/en/get-started/quickstart/contributing-to-projects).
    17  
    18  ### Writing Rules
    19  
    20  Writing a new rule can be relatively simple, but there are a few things to keep in mind. The following guide will help you get started.
    21  
    22  First of all, you should check if the provider your rule targets is supported by _defsec_. If it's not, you'll need to add support for it. See [Adding Support for a New Cloud Provider](#adding-support-for-a-new-cloud-provider) for more information. You can check if support exists by looking for a directory with the provider name in `pkg/providers`.  If you find your provider, navigate into the directory and check for a directory with the name of the service you're targeting. If you can't find that, you'll need to add support for it. See [Adding Support for a New Service](#adding-support-for-a-new-service) for more information.
    23  
    24  Next up, you'll need to check if the properties you want to target are supported, and if not, add support for them. The guide on [Adding Support for a New Service](#adding-support-for-a-new-service) covers adding new properties.
    25  
    26  At last, it's time to write your rule code! Rules are defined using _OPA Rego_. You can find a number of examples in the `rules/cloud/policies` directory. The [OPA documentation](https://www.openpolicyagent.org/docs/latest/policy-language/) is a great place to start learning Rego. You can also check out the [Rego Playground](https://play.openpolicyagent.org/) to experiment with Rego, and [join the OPA Slack](https://slack.openpolicyagent.org/).
    27  
    28  Create a new file in `rules/cloud/policies` with the name of your rule. You should nest it in the existing directory structure as applicable. The package name should be in the format `builtin.PROVIDER.SERVICE.ID`, e.g. `builtin.aws.rds.aws0176`.
    29  
    30  Running `make id` will provide you with the next available _ID_ for your rule. You can use this ID in your rule code to identify it. 
    31  
    32  A simple rule looks like the following example:
    33  
    34  ```rego
    35  # METADATA
    36  # title: "RDS IAM Database Authentication Disabled"
    37  # description: "Ensure IAM Database Authentication is enabled for RDS database instances to manage database access"
    38  # scope: package
    39  # schemas:
    40  # - input: schema.input
    41  # related_resources:
    42  # - https://docs.aws.amazon.com/neptune/latest/userguide/iam-auth.html
    43  # custom:
    44  #   avd_id: AVD-AWS-0176
    45  #   provider: aws
    46  #   service: rds
    47  #   severity: MEDIUM
    48  #   short_code: enable-iam-auth
    49  #   recommended_action: "Modify the PostgreSQL and MySQL type RDS instances to enable IAM database authentication."
    50  #   input:
    51  #     selector:
    52  #     - type: cloud
    53  package builtin.aws.rds.aws0176
    54  
    55  deny[res] {
    56  	instance := input.aws.rds.instances[_]
    57  	instance.engine.value == ["postgres", "mysql"][_]
    58  	not instance.iamauthenabled.value
    59  	res := result.new("Instance does not have IAM Authentication enabled", instance.iamauthenabled)
    60  }
    61  ```
    62  
    63  In fact, this is the code for an actual rule. You can find it in `rules/cloud/policies/aws/rds/enable_iam_auth.rego`. 
    64  
    65  The metadata is the top section that starts with `# METADATA`, and is fairly verbose. You can copy and paste from another rule as a starting point. This format is effectively _yaml_ within a Rego comment, and is [defined as part of Rego itself](https://www.openpolicyagent.org/docs/latest/policy-language/#metadata).
    66  
    67  Let's break the metadata down. 
    68  
    69  - `title` is fairly self explanatory - it is a title for the rule. The title should clearly and succinctly state the problem which is being detected.
    70  - `description` is also fairly self explanatory - it is a description of the problem which is being detected. The description should be a little more verbose than the title, and should describe what the rule is trying to achieve. Imagine it completing a sentence starting with `You should...`.
    71  - `scope` is used to define the scope of the policy. In this case, we are defining a policy that applies to the entire package. _defsec_ only supports using package scope for metadata at the moment, so this should always be the same.
    72  - `schemas` tells Rego that it should use the `schema.input` to validate the use of the input data in the policy. Generally you can use this as-is in order to detect errors in your policy, such as referencing a policy which doesn't exist in the defsec schema.
    73  - `custom` is used to define custom fields that can be used by defsec to provide additional context to the policy and any related detections. This can contain the following:
    74    - `avd_id` is the ID of the rule in the [AWS Vulnerability Database](https://avd.khulnasoft.com/). This is used to link the rule to the AVD entry. You can generate an ID to use for this field using `make id`.
    75    - `provider` is the name of the provider the rule targets. This should be the same as the provider name in the `pkg/providers` directory, e.g. `aws`.
    76    - `service` is the name of the service the rule targets. This should be the same as the service name in the `pkg/providers` directory, e.g. `rds`.
    77    - `severity` is the severity of the rule. This should be one of `LOW`, `MEDIUM`, `HIGH`, or `CRITICAL`.
    78    - `short_code` is a short code for the rule. This should be a short, descriptive name for the rule, separating words with hyphens. You should omit provider/service from this.
    79    - `recommended_action` is a recommended remediation action for the rule. This should be a short, descriptive sentence describing what the user should do to resolve the issue.
    80    - `input` tells _defsec_ what inputs this rule should be applied to. Cloud provider rules should always use the `selector` input, and should always use the `type` selector with `cloud`. Rules targeting Kubernetes yaml can use `kubenetes`, RBAC can use `rbac`, and so on. 
    81  
    82  Now you'll need to write the rule logic. This is the code that will be executed to detect the issue. You should define a rule named `deny` and place your code inside this. 
    83  
    84  ```rego
    85  deny[res] {
    86  	instance := input.aws.rds.instances[_]
    87  	instance.engine.value == ["postgres", "mysql"][_]
    88  	not instance.iamauthenabled.value
    89  	res := result.new("Instance does not have IAM Authentication enabled", instance.iamauthenabled)
    90  }
    91  ```
    92  
    93  The rule should return a result, which can be created using `result.new` (this function does not need to be imported, it is defined internally and provided at runtime). The first argument is the message to display, and the second argument is the resource that the issue was detected on.
    94  
    95  In the example above, you'll notice properties are being accessed from the `input.aws` object. The full set of schemas containing all of these properties is [available here](https://github.com/khulnasoft-lab/defsec/tree/master/pkg/rego/schemas). You can match the schema name to the type of input you want to scan.
    96  
    97  You should also write a test for your rule(s). There are many examples of these in the `rules/cloud/policies` directory.
    98  
    99  Finally, you'll want to run `make docs` to generate the documentation for your new policy.
   100  
   101  You can see a full example PR for a new rule being added here: [https://github.com/khulnasoft-lab/defsec/pull/1000](https://github.com/khulnasoft-lab/defsec/pull/1000).
   102  
   103  
   104  ### Adding Support for a New Cloud Provider
   105  
   106  If you want to add support for a new cloud provider, you'll need to add a new subdirectory to the `pkg/providers` directory, named after your provider. Inside this, create a Go file with the same name, and create a struct to hold information about all of the services supported by your provider.
   107  
   108  For example, adding support for a new provider called `foo` would look like this:
   109  
   110  `pkg/providers/foo/foo.go`:
   111  
   112  ```go
   113  package foo
   114  
   115  type Foo struct {
   116  	// Add services here later...
   117  }
   118  ```
   119  
   120  Next you should add a reference to your provider struct in `pkg/state/state.go`:
   121  
   122  ```go
   123  type State struct {
   124  	// ...
   125      Foo foo.Foo
   126  	// ...
   127  }
   128  ```
   129  
   130  Next up you'll need to add one or more _adapters_ to `internal/adapters`. An adapter takes an input and populates your provider struct. For example, if you want to scan a Terraform plan, you'll need to add an adapter that takes the Terraform plan and populates your provider struct. The AWS provider support in _defsec_ uses multiple adapters - it can adapt CloudFormation, Terraform, and live AWS accounts. Each of these has an adapter in this directory.
   131  
   132  To support Terraform as an input, your adapter should look something like this:
   133  
   134  ```go
   135  func Adapt(modules terraform.Modules) (foo.Foo, error) {
   136      return foo.Foo{
   137  		// ...
   138      }, nil
   139  }
   140  ```
   141  
   142  ...and should be called in `internal/adapters/terraform/adapt.go`. 
   143  
   144  It's a good idea to browse the existing adapters to see how they work, as there is a lot of common code that can be reused.
   145  
   146  ### Adding Support for a New Service
   147  
   148  Adding a new service involves two steps. The service will need a data structure to store information about the required resources, and then one or more adapters to convert input(s) into the aforementioned data structure.
   149  
   150  To add a new service named `bar` to a provider named `foo`, you'll need to add a new file at `pkg/providers/foo/bar/bar.go`:
   151  
   152  ```go
   153  type Bar struct {
   154      // ...
   155  }
   156  ```
   157  
   158  Let's say the `Bar` service manages resources called `Baz`. You'll need to add a new struct to the `Bar` struct to hold information about this resource:
   159  
   160  ```go
   161  type Bar struct {
   162      // ...
   163      Baz []Baz
   164      // ...
   165  }
   166  
   167  type Baz struct {
   168      types.Metadata
   169  	Name types.StringValue
   170  	Encrypted types.BoolValue
   171  }
   172  ```
   173  
   174  A _Baz_ can have a name, and can optionally be encrypted. Instead of using raw `string` and `bool` types respectively, we use the _defsec_ types `types.StringValue` and `types.BoolValue`. These types wrap the raw values and provide additional metadata about the value, such as whether it was set by the user or not, and the file and line number where the resource was defined. The `types.Metadata` struct is embedded in all of the _defsec_ types, and provides a common set of metadata for all resources. This includes the file and line number where the resource was defined, and the name of the resource.
   175  
   176  Next you'll need to add a reference to your new service struct in the provider struct at `pkg/providers/foo/foo.go`:
   177  
   178  ```go
   179  type Foo struct {
   180      // ...
   181      Bar bar.Bar
   182      // ...
   183  }
   184  ```
   185  
   186  Now you'll need to update all of the adapters which populate the `Foo` provider struct. For example, if you want to support Terraform, you'll need to update `internal/adapters/terraform/foo/bar/adapt.go`.
   187  
   188  Finally, make sure you run `make schema` to generate the schema for your new service.