Skip to content

Commit 4cd05d6

Browse files
committed
Added harvester-restricted-network-vm
1 parent b81b667 commit 4cd05d6

File tree

15 files changed

+916
-0
lines changed

15 files changed

+916
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Binaries for programs and plugins
2+
*.exe
3+
*.exe~
4+
*.dll
5+
*.so
6+
*.dylib
7+
*.wasm
8+
9+
# Test binary, built with `go test -c`
10+
*.test
11+
12+
# Output of the go coverage tool, specifically when used with LiteIDE
13+
*.out
14+
15+
# Dependency directories (remove the comment below to include it)
16+
# vendor/
17+
18+
bin/
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../Makefile.tinygo
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# harvester-restricted-network-vm
2+
3+
This policy guards against VMs being deployed into protected network segments.
4+
5+
**Example policy:**
6+
7+
```
8+
apiVersion: policies.kubewarden.io/v1
9+
kind: ClusterAdmissionPolicy
10+
metadata:
11+
name: restricted-network-vm-policy-1
12+
spec:
13+
module: harvester-restricted-network-vm:20
14+
rules:
15+
- apiGroups: ["kubevirt.io"]
16+
apiVersions: ["v1"]
17+
resources: ["virtualmachines"]
18+
operations: ["CREATE", "UPDATE"]
19+
settings:
20+
namespaceNetworkBindings:
21+
- namespace: test-restricted-1-network-1
22+
network: test-restricted-1-network-1/network-1
23+
- namespace: test-restricted-2-network-1
24+
network: test-restricted-1-network-1/network-1
25+
- namespace: test-restricted-3-network-3
26+
network: test-restricted-3-network-3/network-3
27+
mutating: false
28+
policyServer: default
29+
```
30+
31+
**Specifications:**
32+
33+
1. You should be able to create a VM with any of the specific combinations there
34+
2. You should not be able to create a VM from any namespace or network that is in that list, but the exact combination is not in the list.
35+
3. Any namespace or network that is not on the list is not restricted
36+
37+
**Examples:**
38+
39+
| namespace | network | Result |
40+
|-----------------------------|---------------------------------------|--------|
41+
| test-restricted-1-network-1 | test-restricted-1-network-1/network-1 | ALLOW |
42+
| test-restricted-2-network-1 | test-restricted-1-network-1/network-1 | ALLOW |
43+
| test-restricted-3-network-3 | test-restricted-3-network-3/network-3 | ALLOW |
44+
| random-namespace | random-network | ALLOW |
45+
| test-restricted-3-network-3 | test-restricted-1-network-1/network-1 | REJECT |
46+
| test-restricted-1-network-1 | test-restricted-3-network-3/network-3 | REJECT |
47+
| random-namespace | test-restricted-1-network-1/network-1 | REJECT |
48+
| random-namespace | test-restricted-3-network-3/network-3 | REJECT |
49+
| test-restricted-1-network-1 | random-network | REJECT |
50+
| test-restricted-2-network-2 | random-network | REJECT |
51+
| test-restricted-3-network-3 | random-network | REJECT |
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package main
2+
3+
import (
4+
"context"
5+
6+
"github.com/SUSE/openplatform-kubewarden-policies/policies/harvester-restricted-network-vm/internal/logger"
7+
"github.com/wapc/wapc-guest-tinygo"
8+
)
9+
10+
func main() {
11+
ctx := logger.ContextWithLogger(context.Background())
12+
13+
wapc.RegisterFunctions(wapc.Functions{
14+
"validate": func(payload []byte) ([]byte, error) {
15+
return validate(ctx, payload)
16+
},
17+
"validate_settings": func(payload []byte) ([]byte, error) {
18+
return validateSettings(ctx, payload)
19+
},
20+
})
21+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/SUSE/openplatform-kubewarden-policies/policies/harvester-restricted-network-vm/internal/logger"
9+
"github.com/francoispqt/onelog"
10+
kubewarden "github.com/kubewarden/policy-sdk-go"
11+
kubewardenProtocol "github.com/kubewarden/policy-sdk-go/protocol"
12+
)
13+
14+
type SettingsError struct {
15+
Namespace string
16+
Network string
17+
}
18+
19+
func (s *SettingsError) Error() string {
20+
return fmt.Sprintf(
21+
"Invalid settings error, namespace and network must be specified. Namespace: '%s', Network: '%s'",
22+
s.Namespace,
23+
s.Network,
24+
)
25+
}
26+
27+
type NamespaceNetworkBinding struct {
28+
Namespace string `json:"namespace"`
29+
Network string `json:"network"`
30+
}
31+
32+
// Settings is the structure that describes the policy settings.
33+
type Settings struct {
34+
NamespaceNetworkBindings []NamespaceNetworkBinding `json:"namespaceNetworkBindings"`
35+
}
36+
37+
func (s *Settings) valid() (bool, error) {
38+
for _, ns := range s.NamespaceNetworkBindings {
39+
// Check if namespace and network are not empty
40+
if ns.Namespace == "" || ns.Network == "" {
41+
return false, &SettingsError{
42+
Namespace: ns.Namespace,
43+
Network: ns.Network,
44+
}
45+
}
46+
}
47+
return true, nil
48+
}
49+
50+
func NewSettingsFromValidationReq(validationReq *kubewardenProtocol.ValidationRequest) (Settings, error) {
51+
settings := Settings{}
52+
err := json.Unmarshal(validationReq.Settings, &settings)
53+
return settings, err
54+
}
55+
56+
func validateSettings(ctx context.Context, payload []byte) ([]byte, error) {
57+
l := logger.FromContext(ctx)
58+
l.Info("validating settings")
59+
60+
settings := Settings{}
61+
err := json.Unmarshal(payload, &settings)
62+
if err != nil {
63+
return kubewarden.RejectSettings(kubewarden.Message(fmt.Sprintf("Invalid settings JSON: %v", err)))
64+
}
65+
66+
valid, err := settings.valid()
67+
if !valid || err != nil {
68+
return kubewarden.RejectSettings("settings are not valid")
69+
}
70+
71+
return kubewarden.AcceptSettings()
72+
}
73+
74+
// isNetworkAllowed verifies if a (namespace, network) combination is allowed.
75+
//
76+
// Restrictions
77+
// - namespaces with network bindings can only accept a network bound to it
78+
// - networks with namespace bindings can only accept a namespace bound to it
79+
// - If a namespace and network don't have a binding, then it's unrestricted
80+
//
81+
// example:
82+
//
83+
// settings:
84+
// - namespace: restricted-namespace
85+
// network: restricted-network
86+
//
87+
// Allowed:
88+
//
89+
// {"namespace": "restricted-namespace", "network": "restricted-network"}
90+
// {"namespace": "random-namespace", "network": "random-network"}
91+
//
92+
// Denied:
93+
//
94+
// {"namespace": "restricted-namespace", "network": "random-network"}
95+
// {"namespace": "random-namespace", "network": "restricted-network"}
96+
func (s *Settings) isNetworkAllowed(ctx context.Context, namespace, network string) bool {
97+
l := logger.FromContext(ctx).With(func(entry onelog.Entry) {
98+
entry.String("namespace", namespace)
99+
entry.String("network", network)
100+
})
101+
allowed := true
102+
103+
for _, ns := range s.NamespaceNetworkBindings {
104+
// if a namespace is bound, then its network must be bound to it
105+
if ns.Namespace == namespace && ns.Network != network {
106+
allowed = false
107+
}
108+
109+
// if a network is bound, then its namespace must be bound to it
110+
if ns.Network == network && ns.Namespace != namespace {
111+
allowed = false
112+
}
113+
114+
// network and namespace are bound
115+
if ns.Network == network && ns.Namespace == namespace {
116+
l.Debug("network and namespace matched")
117+
return true
118+
}
119+
}
120+
121+
// if allowed is "true", it's because the namespace and network are not bound and are considered unrestricted
122+
if allowed {
123+
l.Debug("namespace and network are unrestricted")
124+
return true
125+
}
126+
127+
l.Debug("namespace or network is restricted and cannot bound together")
128+
return false
129+
}

0 commit comments

Comments
 (0)