1package firewall
2
3import (
4	"fmt"
5	"net"
6	"reflect"
7
8	"github.com/hetznercloud/hcloud-go/hcloud"
9	"github.com/spf13/cobra"
10
11	"github.com/hetznercloud/cli/internal/cmd/cmpl"
12	"github.com/hetznercloud/cli/internal/cmd/util"
13	"github.com/hetznercloud/cli/internal/state"
14)
15
16func newDeleteRuleCommand(cli *state.State) *cobra.Command {
17	cmd := &cobra.Command{
18		Use:                   "delete-rule FIREWALL FLAGS",
19		Short:                 "Delete a single rule to a firewall",
20		Args:                  cobra.ExactArgs(1),
21		ValidArgsFunction:     cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.FirewallNames)),
22		TraverseChildren:      true,
23		DisableFlagsInUseLine: true,
24		PreRunE:               util.ChainRunE(cli.EnsureToken),
25		RunE:                  cli.Wrap(runDeleteRule),
26	}
27	cmd.Flags().String("direction", "", "Direction (in, out) (required)")
28	cmd.RegisterFlagCompletionFunc("direction", cmpl.SuggestCandidates("in", "out"))
29	cmd.MarkFlagRequired("direction")
30
31	cmd.Flags().String("protocol", "", "Protocol (icmp, udp or tcp) (required)")
32	cmd.RegisterFlagCompletionFunc("protocol", cmpl.SuggestCandidates("icmp", "udp", "tcp"))
33	cmd.MarkFlagRequired("protocol")
34
35	cmd.Flags().StringArray("source-ips", []string{}, "Source IPs (CIDR Notation) (required when direction is in)")
36
37	cmd.Flags().StringArray("destination-ips", []string{}, "Destination IPs (CIDR Notation) (required when direction is out)")
38
39	cmd.Flags().String("port", "", "Port to which traffic will be allowed, only applicable for protocols TCP and UDP")
40	return cmd
41}
42
43func runDeleteRule(cli *state.State, cmd *cobra.Command, args []string) error {
44	direction, _ := cmd.Flags().GetString("direction")
45	protocol, _ := cmd.Flags().GetString("protocol")
46	sourceIPs, _ := cmd.Flags().GetStringArray("source-ips")
47	destinationIPs, _ := cmd.Flags().GetStringArray("destination-ips")
48	port, _ := cmd.Flags().GetString("port")
49
50	idOrName := args[0]
51	firewall, _, err := cli.Client().Firewall.Get(cli.Context, idOrName)
52	if err != nil {
53		return err
54	}
55	if firewall == nil {
56		return fmt.Errorf("Firewall not found: %v", idOrName)
57	}
58
59	var sourceNets []net.IPNet
60	for i, sourceIP := range sourceIPs {
61		_, sourceNet, err := net.ParseCIDR(sourceIP)
62		if err != nil {
63			return fmt.Errorf("invalid CIDR on index %d : %s", i, err)
64		}
65		sourceNets = append(sourceNets, *sourceNet)
66	}
67	d := hcloud.FirewallRuleDirection(direction)
68	rule := hcloud.FirewallRule{
69		Direction: d,
70		Protocol:  hcloud.FirewallRuleProtocol(protocol),
71	}
72	if port != "" {
73		rule.Port = hcloud.String(port)
74	}
75	switch d {
76	case hcloud.FirewallRuleDirectionOut:
77		rule.DestinationIPs = make([]net.IPNet, 0, len(destinationIPs))
78		for i, ip := range destinationIPs {
79			_, n, err := net.ParseCIDR(ip)
80			if err != nil {
81				return fmt.Errorf("invalid CIDR on index %d : %s", i, err)
82			}
83			rule.DestinationIPs[i] = *n
84		}
85	case hcloud.FirewallRuleDirectionIn:
86		rule.SourceIPs = make([]net.IPNet, 0, len(sourceIPs))
87		for i, ip := range sourceIPs {
88			_, n, err := net.ParseCIDR(ip)
89			if err != nil {
90				return fmt.Errorf("invalid CIDR on index %d : %s", i, err)
91			}
92			rule.SourceIPs[i] = *n
93		}
94	}
95
96	var rules []hcloud.FirewallRule
97	for _, existingRule := range firewall.Rules {
98		if !reflect.DeepEqual(existingRule, rule) {
99			rules = append(rules, existingRule)
100		}
101	}
102	if len(rules) == len(firewall.Rules) {
103		return fmt.Errorf("the specified rule was not found in the ruleset of Firewall %d", firewall.ID)
104	}
105	actions, _, err := cli.Client().Firewall.SetRules(cli.Context, firewall,
106		hcloud.FirewallSetRulesOpts{Rules: rules},
107	)
108	if err != nil {
109		return err
110	}
111	if err := cli.ActionsProgresses(cli.Context, actions); err != nil {
112		return err
113	}
114	fmt.Printf("Firewall Rules for Firewall %d updated\n", firewall.ID)
115
116	return nil
117}
118