Skip to content

Commit

Permalink
⭐ introduce sshd.config.blocks (#3194)
Browse files Browse the repository at this point in the history
* ⭐ introduce sshd.config.blocks

This introduces support for querying individual blocks in SSHd configs.
It's a more direct way of adressing feedback in
mondoohq/cnspec-policies#340 by exposing the
underlying block entirely, while also supporting aggregate values in the
existing params structure.

Example: Let's assume we have an existing `/etc/ssh/sshd_config` on our
system with a bunch of existing configuration. If we added a new match
group at the end of the file like this:

```ini
Match Group sftp-users
X11Forwarding no
PermitRootLogin no
AllowTCPForwarding yes
```

We can now query this match block both explicitly and implicitly.

Implicitly it's (already) represented in the existing `params` field:

```coffee
> sshd.config.params.AllowTcpForwarding
"no,yes"
```

In the above example you can see, that we already had this field set
above the match block with the value set to `no`. After adding our match
group, it was additionally set to `yes`. The field aggregates both
values.

This implicit access to config values has already existed in MQL as the
default behavior.

With the new `blocks` field, we are extending implicit match block
access to become explicit:

```coffee
> sshd.config.blocks
sshd.config.blocks: [
  0: sshd.config.matchBlock criteria=""
  1: sshd.config.matchBlock criteria="Group sftp-users"
]
```

This first match block is the default block, which is always present. It
has no criteria set and applies to everything.

The second match block has a `criteria` field that shows it only matches
for `Group sftp-users`.

You can easily access its configuration:

```coffee
> sshd.config.blocks { criteria params }
sshd.config.blocks: [
  0: {
    criteria: ""
    params: {
      AllowTcpForwarding: "no"
      ...
    }
  }
  1: {
    criteria: "Group sftp-users"
    params: {
      AllowTcpForwarding: "yes"
      PermitRootLogin: "no"
      X11Forwarding: "no"
    }
  }
]
```

In this example you can see that each block contains its own set of
parameters. These are now restricted to the configuration of the block
only. Thus the `AllowTcpForwarding` setting is not an aggregate of
values anymore, it now only contains the value defined in the block.

Added Note: As a consequence of this change we are now also consistently
structuring the `Match` field in the `sshd.config.params` structure to
behave like all other fields: It combines any match group separated by
commas:

```coffee
> sshd.config.params.Match
sshd.config.params[Match]: "Group sftp-users,User myservice"
```

Signed-off-by: Dominik Richter <[email protected]>

* 🟢 fix ssh params test

Signed-off-by: Dominik Richter <[email protected]>

* 🟢 fix mqlc tests for new fields

Signed-off-by: Dominik Richter <[email protected]>

---------

Signed-off-by: Dominik Richter <[email protected]>
  • Loading branch information
arlimus authored Feb 5, 2024
1 parent 40ad9f1 commit 784d7fa
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 33 deletions.
2 changes: 1 addition & 1 deletion mqlc/mqlc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1912,7 +1912,7 @@ func TestSuggestions(t *testing.T) {
{
// resource suggestions
"ssh",
[]string{"os.unix.sshd", "sshd", "sshd.config", "windows.security.health"},
[]string{"os.unix.sshd", "sshd", "sshd.config", "sshd.config.matchBlock", "windows.security.health"},
errors.New("cannot find resource for identifier 'ssh'"),
nil,
},
Expand Down
4 changes: 2 additions & 2 deletions providers-sdk/v1/testutils/testdata/arch.json

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions providers/os/resources/os.lr
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,8 @@ sshd.config {
content(files) string
// Configuration values of this SSH server
params(content) map[string]string
// Blocks with match conditions in this SSH server config
blocks(content) []sshd.config.matchBlock
// Ciphers configured for this SSH server
ciphers(params) []string
// MACs configured for this SSH server
Expand All @@ -684,6 +686,13 @@ sshd.config {
permitRootLogin(params) []string
}

private sshd.config.matchBlock @defaults("criteria") {
// The match criteria for this block
criteria string
// Configuration values in this block
params map[string]string
}

// Service on this system
service @defaults("name running enabled type") {
init(name string)
Expand Down
102 changes: 101 additions & 1 deletion providers/os/resources/os.lr.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions providers/os/resources/os.lr.manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,8 @@ resources:
min_mondoo_version: 5.15.0
sshd.config:
fields:
blocks:
min_mondoo_version: latest
ciphers: {}
content: {}
file: {}
Expand All @@ -853,6 +855,13 @@ resources:
snippets:
- query: sshd.config.params['Banner'] == '/etc/ssh/sshd-banner'
title: Check that the SSH banner is sourced from /etc/ssh/sshd-banner
sshd.config.matchBlock:
fields:
condition: {}
criteria: {}
params: {}
is_private: true
min_mondoo_version: latest
user:
fields:
authorizedkeys: {}
Expand Down
54 changes: 45 additions & 9 deletions providers/os/resources/sshd.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ import (
"errors"
"fmt"
"strings"
"sync"

"go.mondoo.com/cnquery/v10/llx"
"go.mondoo.com/cnquery/v10/providers-sdk/v1/plugin"
"go.mondoo.com/cnquery/v10/providers/os/connection/shared"
"go.mondoo.com/cnquery/v10/providers/os/resources/sshd"
"go.mondoo.com/cnquery/v10/types"
)

type mqlSshdConfigInternal struct {
lock sync.Mutex
}

func initSshdConfig(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) {
if x, ok := args["path"]; ok {
path, ok := x.Value.(string)
Expand Down Expand Up @@ -117,19 +123,49 @@ func (s *mqlSshdConfig) content(files []interface{}) (string, error) {
return fullContent, nil
}

func (s *mqlSshdConfig) params(content string) (map[string]interface{}, error) {
params, err := sshd.Params(content)
if err != nil {
return nil, err
func matchBlocks2Resources(m sshd.MatchBlocks, runtime *plugin.Runtime, ownerID string) ([]any, error) {
res := make([]any, len(m))
for i := range m {
cur := m[i]
obj, err := CreateResource(runtime, "sshd.config.matchBlock", map[string]*llx.RawData{
"__id": llx.StringData(ownerID + "\x00" + cur.Criteria),
"criteria": llx.StringData(cur.Criteria),
"params": llx.MapData(cur.Params, types.String),
})
if err != nil {
return nil, err
}
res[i] = obj
}
return res, nil
}

// convert map
res := map[string]interface{}{}
for k, v := range params {
res[k] = v
func (s *mqlSshdConfig) parse(content string) error {
s.lock.Lock()
defer s.lock.Unlock()

params, err := sshd.ParseBlocks(content)
if err != nil {
s.Params = plugin.TValue[map[string]any]{Error: err, State: plugin.StateIsSet | plugin.StateIsNull}
s.Blocks = plugin.TValue[[]any]{Error: err, State: plugin.StateIsSet | plugin.StateIsNull}
} else {
blocks, err := matchBlocks2Resources(params, s.MqlRuntime, s.__id)
if err != nil {
return err
}
s.Params = plugin.TValue[map[string]any]{Data: params.Flatten(), State: plugin.StateIsSet}
s.Blocks = plugin.TValue[[]any]{Data: blocks, State: plugin.StateIsSet}
}

return res, nil
return err
}

func (s *mqlSshdConfig) params(content string) (map[string]any, error) {
return nil, s.parse(content)
}

func (s *mqlSshdConfig) blocks(content string) ([]any, error) {
return nil, s.parse(content)
}

func (s *mqlSshdConfig) parseConfigEntrySlice(raw interface{}) ([]interface{}, error) {
Expand Down
Loading

0 comments on commit 784d7fa

Please sign in to comment.