Skip to content

Commit 0759ebd

Browse files
committed
[tools] Add tools subcommand with falco audit rules converter
Signed-off-by: Maxim Vasilenko <[email protected]>
1 parent 64ce6e0 commit 0759ebd

File tree

6 files changed

+316
-2
lines changed

6 files changed

+316
-2
lines changed

cmd/tools.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
Copyright 2025 Flant JSC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cmd
18+
19+
import "github.com/deckhouse/deckhouse-cli/internal/tools"
20+
21+
func init() {
22+
rootCmd.AddCommand(tools.NewCommand())
23+
}

go.mod

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ require (
88
github.com/Masterminds/semver/v3 v3.3.0
99
github.com/deckhouse/virtualization/src/cli v0.0.0-20250617111832-70fdc2799bf3
1010
github.com/google/go-containerregistry v0.20.0
11+
github.com/gosimple/slug v1.15.0
1112
github.com/hashicorp/go-cleanhttp v0.5.2
1213
github.com/hashicorp/go-multierror v1.1.1
1314
github.com/hashicorp/vault v1.14.8
15+
github.com/iancoleman/strcase v0.3.0
1416
github.com/int128/kubelogin v1.28.0
1517
github.com/pkg/errors v0.9.1
1618
github.com/samber/lo v1.47.0
@@ -26,13 +28,13 @@ require (
2628
go.cypherpunks.ru/gogost/v5 v5.13.0
2729
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
2830
golang.org/x/term v0.30.0
31+
gopkg.in/yaml.v3 v3.0.1
2932
k8s.io/api v0.33.0
3033
k8s.io/apiextensions-apiserver v0.33.0
3134
k8s.io/apimachinery v0.33.0
3235
k8s.io/cli-runtime v0.29.3
3336
k8s.io/client-go v0.33.0
3437
k8s.io/component-base v0.33.0
35-
k8s.io/klog v1.0.0
3638
k8s.io/kubectl v0.29.3
3739
k8s.io/utils v0.0.0-20241210054802-24370beab758
3840
sigs.k8s.io/controller-runtime v0.21.0
@@ -252,6 +254,7 @@ require (
252254
github.com/gophercloud/gophercloud v0.1.0 // indirect
253255
github.com/gorilla/mux v1.8.1 // indirect
254256
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
257+
github.com/gosimple/unidecode v1.0.1 // indirect
255258
github.com/gosuri/uitable v0.0.4 // indirect
256259
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
257260
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
@@ -575,9 +578,9 @@ require (
575578
gopkg.in/resty.v1 v1.12.0 // indirect
576579
gopkg.in/warnings.v0 v0.1.2 // indirect
577580
gopkg.in/yaml.v2 v2.4.0 // indirect
578-
gopkg.in/yaml.v3 v3.0.1 // indirect
579581
k8s.io/apiserver v0.33.0 // indirect
580582
k8s.io/component-helpers v0.29.3 // indirect
583+
k8s.io/klog v1.0.0 // indirect
581584
k8s.io/klog/v2 v2.130.1 // indirect
582585
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
583586
k8s.io/metrics v0.29.3 // indirect

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,10 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
792792
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
793793
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
794794
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
795+
github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
796+
github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
797+
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
798+
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
795799
github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
796800
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
797801
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
@@ -997,6 +1001,8 @@ github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq
9971001
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
9981002
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
9991003
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
1004+
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
1005+
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
10001006
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
10011007
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
10021008
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package farconverter
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"log"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
11+
"github.com/gosimple/slug"
12+
"github.com/iancoleman/strcase"
13+
"github.com/spf13/cobra"
14+
"gopkg.in/yaml.v3"
15+
)
16+
17+
// RawRule represents decoded rules in Falco format.
18+
type RawRule map[string]any
19+
20+
// FalcoAuditRule is a structure to encode FalcoAuditRules custom resources.
21+
type FalcoAuditRule struct {
22+
ApiVersion string `yaml:"apiVersion"`
23+
Kind string
24+
Metadata Metadata
25+
Spec FalcoAuditRuleSpec
26+
}
27+
28+
type FalcoAuditRuleSpecRule struct {
29+
Rule Rule `yaml:"rule,omitempty"`
30+
Macro Macro `yaml:"macro,omitempty"`
31+
List List `yaml:"list,omitempty"`
32+
}
33+
34+
type FalcoAuditRuleSpec struct {
35+
RequiredEngineVersion int `yaml:"requiredEngineVersion,omitempty"`
36+
RequiredK8sAuditPluginVersion string `yaml:"requiredK8sAuditPluginVersion,omitempty"`
37+
Rules []FalcoAuditRuleSpecRule `yaml:"rules"`
38+
}
39+
40+
type Metadata struct {
41+
Name string
42+
}
43+
44+
type Rule struct {
45+
Name string `yaml:"name"`
46+
Condition string `yaml:"condition"`
47+
Desc string `yaml:"desc"`
48+
Output string `yaml:"output"`
49+
Priority string `yaml:"priority"`
50+
Enabled bool `yaml:"enabled,omitempty"`
51+
Tags []any `yaml:"tags,omitempty"`
52+
Source string `yaml:"source,omitempty"`
53+
}
54+
55+
type Macro struct {
56+
Name string `yaml:"name"`
57+
Condition string `yaml:"condition"`
58+
}
59+
60+
type List struct {
61+
Name string `yaml:"name"`
62+
Items []any `yaml:"items"`
63+
}
64+
65+
func Convert(cmd *cobra.Command, args []string) error {
66+
input := args[0]
67+
var rules []RawRule
68+
log.Printf("Convert rules from %q", input)
69+
70+
data, err := os.ReadFile(input)
71+
if err != nil {
72+
return err
73+
}
74+
75+
err = yaml.Unmarshal(data, &rules)
76+
if err != nil {
77+
return err
78+
}
79+
80+
buf := bytes.Buffer{}
81+
enc := yaml.NewEncoder(&buf)
82+
enc.SetIndent(2)
83+
84+
if err := enc.Encode(convert(input, rules)); err != nil {
85+
return err
86+
}
87+
88+
io.Copy(os.Stdout, &buf)
89+
return nil
90+
}
91+
92+
func convert(path string, rules []RawRule) FalcoAuditRule {
93+
result := FalcoAuditRule{
94+
ApiVersion: "deckhouse.io/v1alpha1",
95+
Kind: "FalcoAuditRules",
96+
Metadata: Metadata{
97+
Name: nameFromPath(path),
98+
},
99+
Spec: FalcoAuditRuleSpec{},
100+
}
101+
102+
for _, r := range rules {
103+
if v, ok := r["required_engine_version"]; ok {
104+
result.Spec.RequiredEngineVersion = v.(int)
105+
continue
106+
}
107+
108+
if v, ok := r["required_plugin_versions"]; ok {
109+
for _, p := range v.([]any) {
110+
plugin := p.(map[string]any)
111+
if plugin["name"].(string) == "k8saudit" {
112+
result.Spec.RequiredK8sAuditPluginVersion = plugin["version"].(string)
113+
}
114+
}
115+
continue
116+
}
117+
118+
if _, ok := r["macro"]; ok {
119+
result.Spec.Rules = append(result.Spec.Rules, FalcoAuditRuleSpecRule{
120+
Macro: Macro{
121+
Name: r["macro"].(string),
122+
Condition: r["condition"].(string),
123+
},
124+
})
125+
continue
126+
}
127+
128+
if _, ok := r["list"]; ok {
129+
result.Spec.Rules = append(result.Spec.Rules, FalcoAuditRuleSpecRule{
130+
List: List{
131+
Name: r["list"].(string),
132+
Items: r["items"].([]any),
133+
},
134+
})
135+
continue
136+
}
137+
138+
if _, ok := r["rule"]; ok {
139+
ruleToAdd := Rule{
140+
Name: r["rule"].(string),
141+
Condition: r["condition"].(string),
142+
Desc: r["desc"].(string),
143+
Output: r["output"].(string),
144+
Priority: strcase.ToCamel(strings.ToLower(r["priority"].(string))),
145+
}
146+
147+
if tags, ok := r["tags"]; ok {
148+
ruleToAdd.Tags = tags.([]any)
149+
}
150+
151+
if enabled, ok := r["enabled"]; ok {
152+
ruleToAdd.Enabled = enabled.(bool)
153+
}
154+
155+
if source, ok := r["source"]; ok {
156+
switch strings.ToLower(source.(string)) {
157+
case "k8s_audit":
158+
ruleToAdd.Source = "K8sAudit"
159+
case "syscall":
160+
ruleToAdd.Source = "Syscall"
161+
}
162+
}
163+
164+
if _, ok := r["exceptions"]; ok {
165+
log.Printf("[WARNING] Exceptions are not supported (found in %q rule)", ruleToAdd.Name)
166+
}
167+
168+
result.Spec.Rules = append(result.Spec.Rules, FalcoAuditRuleSpecRule{Rule: ruleToAdd})
169+
continue
170+
}
171+
}
172+
173+
return result
174+
}
175+
176+
func nameFromPath(path string) string {
177+
path = filepath.Base(path)
178+
path, _, _ = strings.Cut(path, ".")
179+
path = slug.Make(path)
180+
path = strcase.ToKebab(path)
181+
return path
182+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
Copyright 2025 Flant JSC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package farconverter
18+
19+
import (
20+
"fmt"
21+
"os"
22+
23+
"github.com/spf13/cobra"
24+
"k8s.io/kubectl/pkg/util/templates"
25+
)
26+
27+
var convertLong = templates.LongDesc(`
28+
Converts files with Falco rules to FalcoAuditRules CRD format.
29+
30+
© Flant JSC 2025`)
31+
32+
func NewCommand() *cobra.Command {
33+
convertCmd := &cobra.Command{
34+
Use: "far-convert",
35+
Short: "Converts files with Falco rules to FalcoAuditRules CRD format",
36+
Long: convertLong,
37+
Args: func(cmd *cobra.Command, args []string) error {
38+
if len(args) != 1 {
39+
return fmt.Errorf("this command requires exactly 1 argument, got %d", len(args))
40+
}
41+
42+
s, err := os.Stat(args[0])
43+
if err != nil {
44+
return fmt.Errorf("Invalid path to rules file: %w", err)
45+
}
46+
47+
if !s.Mode().IsRegular() {
48+
return fmt.Errorf("Argument must point to the rules YAML file")
49+
}
50+
51+
return nil
52+
},
53+
RunE: Convert,
54+
}
55+
56+
return convertCmd
57+
}

internal/tools/tools.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
Copyright 2025 Flant JSC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package tools
18+
19+
import (
20+
"github.com/deckhouse/deckhouse-cli/internal/tools/farconverter"
21+
"github.com/spf13/cobra"
22+
"k8s.io/kubectl/pkg/util/templates"
23+
)
24+
25+
var toolsLong = templates.LongDesc(`
26+
Various useful tools for operating in The Deckhouse Ecosystem.
27+
28+
© Flant JSC 2025`)
29+
30+
func NewCommand() *cobra.Command {
31+
toolsCmd := &cobra.Command{
32+
Use: "tools",
33+
Short: "Various useful tools for operating in The Deckhouse Ecosystem.",
34+
// TODO(mvasl) p and platform are old names of this commands and are left as aliases for backwards compatibility
35+
// with our docs until we update them to use s or system.
36+
Aliases: []string{"t"},
37+
Long: toolsLong,
38+
}
39+
40+
toolsCmd.AddCommand(farconverter.NewCommand())
41+
42+
return toolsCmd
43+
}

0 commit comments

Comments
 (0)