Skip to content

Commit

Permalink
Add API and antctl for NetworkPolicyEvaluation
Browse files Browse the repository at this point in the history
Adds a versioned API and antctl query for NetworkPolicy
evaluation that returns the predicted effective NetworkPolicy
rule, which affects traffic from ns1/pod1 to ns2/pod2.

Signed-off-by: Qiyue Yao <yaoq@vmware.com>
  • Loading branch information
qiyueyao committed Feb 6, 2024
1 parent 7055147 commit f4a8b81
Show file tree
Hide file tree
Showing 52 changed files with 3,700 additions and 930 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@ Also check out [@ProjectAntrea](https://twitter.com/ProjectAntrea) on Twitter!
enable fine-grained visibility into the communication among Kubernetes
workloads. Theia provides visualization for Antrea network flows in Grafana
dashboards, and recommends Network Policies to secure the workloads.
* **Network Policies for virtual machines**: Antrea native policies can be
* **Network Policies for virtual machines**: Antrea-native policies can be
enforced on non-Kubernetes Nodes including VMs and baremetal servers. Project
[Nephe](https://github.com/antrea-io/nephe) implements security policies for
VMs across clouds, leveraging Antrea native policies.
VMs across clouds, leveraging Antrea-native policies.
* **Encryption**: Encryption of inter-Node Pod traffic with IPsec or WireGuard
tunnels.
* **Easy deployment**: Antrea is deployed by applying a single YAML manifest
Expand Down
15 changes: 15 additions & 0 deletions docs/antctl.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ running in three different modes:
- [controllerinfo and agentinfo commands](#controllerinfo-and-agentinfo-commands)
- [NetworkPolicy commands](#networkpolicy-commands)
- [Mapping endpoints to NetworkPolicies](#mapping-endpoints-to-networkpolicies)
- [Analyzing expected NetworkPolicy behavior](#analyzing-expected-networkpolicy-behavior)
- [Dumping Pod network interface information](#dumping-pod-network-interface-information)
- [Dumping OVS flows](#dumping-ovs-flows)
- [OVS packet tracing](#ovs-packet-tracing)
Expand Down Expand Up @@ -263,6 +264,20 @@ Namespace.
This command only works in "controller mode" and **as of now it can only be run
from inside the Antrea Controller Pod, and not from out-of-cluster**.

#### Analyzing expected NetworkPolicy behavior

`antctl` supports analyzing all the existing Antrea-native NetworkPolicies,
Kubernetes NetworkPolicies and AdminNetworkPolicies to predict the effective
policy rule for traffic between source and destination Pods.

```bash
antctl query networkpolicyevaluation -S NAMESPACE/POD -D NAMESPACE/POD
```

If only Pod name is provided, the command will default to the "default" Namespace.

This command only works in "controller mode".

### Dumping Pod network interface information

`antctl` agent command `get podinterface` (or `get pi`) can dump network
Expand Down
2 changes: 1 addition & 1 deletion docs/feature-gates.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ This feature is currently only supported for Nodes running Linux. Windows suppor
Stats API, which can be accessed by kubectl get commands, e.g. `kubectl get networkpolicystats`. The statistical data
includes total number of sessions, packets, and bytes allowed or denied by a NetworkPolicy. It is collected
asynchronously so there may be a delay of up to 1 minute for changes to be reflected in API responses. The feature
supports K8s NetworkPolicies and Antrea native policies, the latter of which requires
supports K8s NetworkPolicies and Antrea-native policies, the latter of which requires
`AntreaPolicy` to be enabled. Usage examples:

```bash
Expand Down
2 changes: 1 addition & 1 deletion docs/multicluster/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

Antrea Multi-cluster implements [Multi-cluster Service API](https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api),
which allows users to create multi-cluster Services that can be accessed cross
clusters in a ClusterSet. Antrea Multi-cluster also extends Antrea native
clusters in a ClusterSet. Antrea Multi-cluster also extends Antrea-native
NetworkPolicy to support Multi-cluster NetworkPolicy rules that apply to
cross-cluster traffic, and ClusterNetworkPolicy replication that allows a
ClusterSet admin to create ClusterNetworkPolicies which are replicated across
Expand Down
2 changes: 1 addition & 1 deletion docs/traceflow-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ will fail. But you can specify a different timeout value, by adding
`timeout: <value-in-seconds>` to the Traceflow `spec`.

In some cases, it might be useful to capture the packets dropped by
NetworkPolicies (inc. K8s NetworkPolicies or Antrea native policies). You can
NetworkPolicies (inc. K8s NetworkPolicies or Antrea-native policies). You can
add `droppedOnly: true` to the live-traffic Traceflow `spec`, then the first
packet that matches the Traceflow spec and is dropped by a NetworkPolicy will
be captured and traced.
Expand Down
2 changes: 1 addition & 1 deletion hack/update-codegen-dockerized.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ MOCKGEN_TARGETS=(
"pkg/agent/util/netlink Interface testing mock_netlink_linux.go"
"pkg/agent/wireguard Interface testing mock_wireguard.go"
"pkg/antctl AntctlClient ."
"pkg/controller/networkpolicy EndpointQuerier testing"
"pkg/controller/networkpolicy EndpointQuerier,PolicyRuleQuerier testing"
"pkg/controller/querier ControllerQuerier testing"
"pkg/flowaggregator/exporter Interface testing"
"pkg/ipfix IPFIXExportingProcess,IPFIXRegistry,IPFIXCollectingProcess,IPFIXAggregationProcess testing"
Expand Down
2 changes: 1 addition & 1 deletion pkg/agent/controller/networkpolicy/audit_logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ func getNetworkPolicyInfo(pktIn *ofctrl.PacketIn, packet *binding.Packet, c *Con
ob.ofPriority = ofPriority
ob.ruleName = ruleName
ob.logLabel = logLabel
// Fill in placeholders for Antrea native policies without log labels,
// Fill in placeholders for Antrea-native policies without log labels,
// K8s NetworkPolicies without rule names or log labels.
fillLogInfoPlaceholders([]*string{&ob.ruleName, &ob.logLabel, &ob.ofPriority})
return nil
Expand Down
37 changes: 34 additions & 3 deletions pkg/antctl/antctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ import (
"antrea.io/antrea/pkg/antctl/transform/version"
cpv1beta "antrea.io/antrea/pkg/apis/controlplane/v1beta2"
systemv1beta1 "antrea.io/antrea/pkg/apis/system/v1beta1"
endpointServer "antrea.io/antrea/pkg/apiserver/handlers/endpoint"
controllerinforest "antrea.io/antrea/pkg/apiserver/registry/system/controllerinfo"
"antrea.io/antrea/pkg/client/clientset/versioned/scheme"
controllernetworkpolicy "antrea.io/antrea/pkg/controller/networkpolicy"
"antrea.io/antrea/pkg/flowaggregator/apiserver/handlers/flowrecords"
"antrea.io/antrea/pkg/flowaggregator/apiserver/handlers/recordmetrics"
)
Expand Down Expand Up @@ -222,7 +222,7 @@ $ antctl get podmulticaststats pod -n namespace`,
},
{
name: "type",
usage: "Get NetworkPolicies with specific type. Type means the type of its source network policy: K8sNP, ACNP, ANNP",
usage: "Get NetworkPolicies with specific type. Type means the type of its source NetworkPolicy: K8sNP, ACNP, ANNP",
shorthand: "T",
},
}, getSortByFlag()),
Expand Down Expand Up @@ -507,7 +507,38 @@ $ antctl get podmulticaststats pod -n namespace`,
outputType: single,
},
},
transformedResponse: reflect.TypeOf(controllernetworkpolicy.EndpointQueryResponse{}),
transformedResponse: reflect.TypeOf(endpointServer.EndpointQueryResponse{}),
},
{
use: "networkpolicyevaluation",
aliases: []string{"networkpoliciesevaluation", "networkpolicyeval", "networkpolicieseval", "netpoleval"},
short: "Analyze effective NetworkPolicy rules.",
long: "Analyze network policies in the cluster and return the rule expected to be effective on the source and destination endpoints provided.",
example: ` Query effective NetworkPolicy rule between two Pods
$ antctl query networkpolicyevaluation -S ns1/pod1 -D ns2/pod2
`,
commandGroup: query,
controllerEndpoint: &endpoint{
resourceEndpoint: &resourceEndpoint{
groupVersionResource: &cpv1beta.NetworkPolicyEvaluationVersionResource,
params: []flagInfo{
{
name: "source",
usage: "Source endpoint of network policies. Can be a (local or remote) Pod (specified by <Namespace>/<name>).",
shorthand: "S",
},
{
name: "destination",
usage: "Source endpoint of network policies. Can be a (local or remote) Pod (specified by <Namespace>/<name>).",
shorthand: "D",
},
},
paramsFormatter: networkpolicy.NewNetworkPolicyEvaluation,
restMethod: restPost,
},
addonTransform: networkpolicy.EvaluationTransform,
},
transformedResponse: reflect.TypeOf(networkpolicy.EvaluationResponse{}),
},
{
use: "flowrecords",
Expand Down
26 changes: 21 additions & 5 deletions pkg/antctl/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,22 +161,38 @@ func (c *client) resourceRequest(e *resourceEndpoint, opt *requestOption) (io.Re
// If timeout is zero, there will be no timeout.
restClient.Client.Timeout = opt.timeout

resGetter := restClient.Get().
var restRequest *rest.Request
if e.restMethod == restGet {
restRequest = restClient.Get()
} else if e.restMethod == restPost {
restRequest = restClient.Post()
}

restRequest = restRequest.
NamespaceIfScoped(opt.args["namespace"], e.namespaced).
Resource(e.groupVersionResource.Resource)

if len(e.resourceName) != 0 {
resGetter = resGetter.Name(e.resourceName)
restRequest = restRequest.Name(e.resourceName)
} else if name, ok := opt.args["name"]; ok {
resGetter = resGetter.Name(name)
restRequest = restRequest.Name(name)
}

for arg, val := range opt.args {
if arg != "name" && arg != "namespace" {
resGetter = resGetter.Param(arg, val)
restRequest = restRequest.Param(arg, val)
}
}
result := resGetter.Do(context.TODO())

if e.paramsFormatter != nil {
obj, err := e.paramsFormatter(opt.args)
if err != nil {
return nil, err
}
restRequest = restRequest.Body(obj)
}

result := restRequest.Do(context.TODO())
if result.Error() != nil {
return nil, generateMessage(opt.commandDefinition, opt.args, true /* isResourceRequest */, result.Error())
}
Expand Down
28 changes: 20 additions & 8 deletions pkg/antctl/command_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ import (

"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/klog/v2"

"antrea.io/antrea/pkg/antctl/output"
"antrea.io/antrea/pkg/antctl/runtime"
"antrea.io/antrea/pkg/apis/controlplane/v1beta2"
"antrea.io/antrea/pkg/controller/networkpolicy"
cpv1beta "antrea.io/antrea/pkg/apis/controlplane/v1beta2"
endpointserver "antrea.io/antrea/pkg/apiserver/handlers/endpoint"
)

type formatterType string
Expand Down Expand Up @@ -108,6 +109,9 @@ type resourceEndpoint struct {
resourceName string
namespaced bool
supportSorting bool
params []flagInfo
paramsFormatter func(args map[string]string) (k8sruntime.Object, error)
restMethod restMethod
}

func (e *resourceEndpoint) OutputType() OutputType {
Expand Down Expand Up @@ -138,6 +142,7 @@ func (e *resourceEndpoint) flags() []flagInfo {
if e.supportSorting {
flags = append(flags, getSortByFlag())
}
flags = append(flags, e.params...)
return flags
}

Expand All @@ -149,6 +154,13 @@ func getSortByFlag() flagInfo {
}
}

type restMethod uint

const (
restGet restMethod = iota
restPost
)

type nonResourceEndpoint struct {
path string
params []flagInfo
Expand Down Expand Up @@ -444,7 +456,7 @@ func (cd *commandDefinition) tableOutputForQueryEndpoint(obj interface{}, writer
return nil
}
// iterate through each endpoint and construct response
endpointQueryResponse := obj.(*networkpolicy.EndpointQueryResponse)
endpointQueryResponse := obj.(*endpointserver.EndpointQueryResponse)
for _, endpoint := range endpointQueryResponse.Endpoints {
// transform applied policies to string representation
policies := make([][]string, 0)
Expand All @@ -455,10 +467,10 @@ func (cd *commandDefinition) tableOutputForQueryEndpoint(obj interface{}, writer
// transform egress and ingress rules to string representation
egress, ingress := make([][]string, 0), make([][]string, 0)
for _, rule := range endpoint.Rules {
ruleStr := []string{rule.Name, rule.Namespace, strconv.Itoa(rule.RuleIndex), string(rule.UID)}
if rule.Direction == v1beta2.DirectionIn {
ruleStr := []string{rule.PolicyRef.Name, rule.PolicyRef.Namespace, strconv.Itoa(rule.RuleIndex), string(rule.PolicyRef.UID)}
if rule.Direction == cpv1beta.DirectionIn {
ingress = append(ingress, ruleStr)
} else if rule.Direction == v1beta2.DirectionOut {
} else if rule.Direction == cpv1beta.DirectionOut {
egress = append(egress, ruleStr)
}
}
Expand Down Expand Up @@ -539,9 +551,10 @@ func (cd *commandDefinition) output(resp io.Reader, writer io.Writer, ft formatt
if cd.commandGroup == get {
return output.TableOutputForGetCommands(obj, writer)
} else if cd.commandGroup == query {
if cd.controllerEndpoint.nonResourceEndpoint.path == "/endpoint" {
if cd.controllerEndpoint.nonResourceEndpoint != nil && cd.controllerEndpoint.nonResourceEndpoint.path == "/endpoint" {
return cd.tableOutputForQueryEndpoint(obj, writer)
}
return output.TableOutputForGetCommands(obj, writer)
} else {
return output.TableOutput(obj, writer)
}
Expand All @@ -550,7 +563,6 @@ func (cd *commandDefinition) output(resp io.Reader, writer io.Writer, ft formatt
default:
return fmt.Errorf("unsupported format type: %v", ft)
}
return nil
}

func (cd *commandDefinition) collectFlags(cmd *cobra.Command, args []string) (map[string]string, error) {
Expand Down
31 changes: 15 additions & 16 deletions pkg/antctl/command_definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import (
"antrea.io/antrea/pkg/antctl/transform/version"
cpv1beta "antrea.io/antrea/pkg/apis/controlplane/v1beta2"
"antrea.io/antrea/pkg/apis/crd/v1beta1"
controllernetworkpolicy "antrea.io/antrea/pkg/controller/networkpolicy"
endpointServer "antrea.io/antrea/pkg/apiserver/handlers/endpoint"
)

type Foobar struct {
Expand Down Expand Up @@ -909,18 +909,18 @@ func TestGetRequestErrorFallback(t *testing.T) {
}

func TestTableOutputForQueryEndpoint(t *testing.T) {
policyRef0 := controllernetworkpolicy.PolicyRef{Namespace: "testNamespace", Name: "test-ingress-egress", UID: "uid-1"}
policyRef1 := controllernetworkpolicy.PolicyRef{Namespace: "testNamespace", Name: "default-deny-egress", UID: "uid-2"}
policyRef0 := cpv1beta.NetworkPolicyReference{Namespace: "testNamespace", Name: "test-ingress-egress", UID: "uid-1", Type: cpv1beta.AntreaNetworkPolicy}
policyRef1 := cpv1beta.NetworkPolicyReference{Namespace: "testNamespace", Name: "default-deny-egress", UID: "uid-2", Type: cpv1beta.AntreaNetworkPolicy}
tc := []struct {
name string
rawResponseData interface{}
expected string
}{
{
name: "Pod selected by no policy",
rawResponseData: &controllernetworkpolicy.EndpointQueryResponse{
Endpoints: []controllernetworkpolicy.Endpoint{
{Namespace: "testNamespace", Name: "podA", Policies: []controllernetworkpolicy.Policy{}, Rules: []controllernetworkpolicy.Rule{}},
rawResponseData: &endpointServer.EndpointQueryResponse{
Endpoints: []endpointServer.Endpoint{
{Namespace: "testNamespace", Name: "podA", Policies: []cpv1beta.NetworkPolicyReference{}, Rules: []endpointServer.Rule{}},
},
},
expected: `Endpoint testNamespace/podA
Expand All @@ -934,13 +934,13 @@ Ingress Rules: None
},
{
name: "Pod selected by 1 policy",
rawResponseData: &controllernetworkpolicy.EndpointQueryResponse{
Endpoints: []controllernetworkpolicy.Endpoint{
rawResponseData: &endpointServer.EndpointQueryResponse{
Endpoints: []endpointServer.Endpoint{
{
Namespace: "testNamespace",
Name: "podA",
Policies: []controllernetworkpolicy.Policy{{PolicyRef: policyRef0}},
Rules: []controllernetworkpolicy.Rule{
Policies: []cpv1beta.NetworkPolicyReference{policyRef0},
Rules: []endpointServer.Rule{
{PolicyRef: policyRef0, Direction: cpv1beta.DirectionOut, RuleIndex: 0},
{PolicyRef: policyRef0, Direction: cpv1beta.DirectionIn, RuleIndex: 0},
},
Expand All @@ -964,16 +964,15 @@ test-ingress-egress testNamespace 0 uid-1
},
{
name: "Pod selected by 2 different policies",
rawResponseData: &controllernetworkpolicy.EndpointQueryResponse{
Endpoints: []controllernetworkpolicy.Endpoint{
rawResponseData: &endpointServer.EndpointQueryResponse{
Endpoints: []endpointServer.Endpoint{
{
Namespace: "testNamespace",
Name: "podA",
Policies: []controllernetworkpolicy.Policy{
{PolicyRef: policyRef0},
{PolicyRef: policyRef1},
Policies: []cpv1beta.NetworkPolicyReference{
policyRef0, policyRef1,
},
Rules: []controllernetworkpolicy.Rule{
Rules: []endpointServer.Rule{
{PolicyRef: policyRef0, Direction: cpv1beta.DirectionOut, RuleIndex: 0},
{PolicyRef: policyRef0, Direction: cpv1beta.DirectionIn, RuleIndex: 0},
},
Expand Down
Loading

0 comments on commit f4a8b81

Please sign in to comment.