/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package model

import (
	"k8s.io/klog/v2"
	"k8s.io/kops/pkg/systemd"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/nodeup/nodetasks"
)

// FirewallBuilder configures the firewall (iptables)
type FirewallBuilder struct {
	*NodeupModelContext
}

var _ fi.NodeupModelBuilder = &FirewallBuilder{}

// Build is responsible for generating any node firewall rules
func (b *FirewallBuilder) Build(c *fi.NodeupModelBuilderContext) error {
	// We need forwarding enabled (https://github.com/kubernetes/kubernetes/issues/40182)
	c.AddTask(b.buildFirewallScript())
	c.AddTask(b.buildSystemdService())

	// On distros where Kubernetes CNIs (notably Calico) document firewalld
	// as incompatible, stop and mask it. firewalld's default-reject
	// filter_INPUT/filter_FORWARD policies and periodic-reload behavior
	// conflict with the iptables/nftables rules CNIs install for pod and
	// service traffic. Most cloud images in the RHEL family already ship
	// firewalld off (AWS RHEL/Rocky AMIs, upstream Rocky GenericCloud); the
	// GCE-optimized Rocky 10 image is the known outlier. The disable/mask
	// sequence is idempotent and a no-op where firewalld isn't installed.
	// See: https://docs.tigera.io/calico/latest/getting-started/kubernetes/requirements
	if b.Distribution.ForceNftables() {
		c.AddTask(&nodetasks.File{
			Path:     "/etc/kops/firewalld-disabled",
			Contents: fi.NewStringResource("# Marker: firewalld disabled by kops to avoid conflicts with the Kubernetes CNI dataplane.\n"),
			Type:     nodetasks.FileType_File,
			OnChangeExecute: [][]string{
				// Stop and disable so it can't restart at boot.
				{"bash", "-c", "systemctl disable --now firewalld.service 2>/dev/null; true"},
				// Mask so package updates or preset reloads can't bring it back.
				{"bash", "-c", "systemctl mask firewalld.service 2>/dev/null; true"},
			},
		})
	}

	return nil
}

func (b *FirewallBuilder) buildSystemdService() *nodetasks.Service {
	manifest := &systemd.Manifest{}
	manifest.Set("Unit", "Description", "Configure iptables for kubernetes")
	manifest.Set("Unit", "Documentation", "https://github.com/kubernetes/kops")
	manifest.Set("Unit", "Before", "network.target")
	manifest.Set("Service", "Type", "oneshot")
	manifest.Set("Service", "RemainAfterExit", "yes")
	manifest.Set("Service", "ExecStart", "/opt/kops/bin/iptables-setup")
	manifest.Set("Install", "WantedBy", "basic.target")

	manifestString := manifest.Render()
	klog.V(8).Infof("Built service manifest %q\n%s", "kubernetes-iptables-setup", manifestString)

	service := &nodetasks.Service{
		Name:       "kubernetes-iptables-setup.service",
		Definition: s(manifestString),
	}

	service.InitDefaults()

	return service
}

func (b *FirewallBuilder) buildFirewallScript() *nodetasks.File {
	// TODO: Do we want to rely on running nodeup on every boot, or do we want to install systemd units?

	// TODO: The if statement in the script doesn't make it idempotent

	// This is borrowed from gce/gci/configure-helper.sh
	script := `#!/bin/bash
# Built by kops - do not edit

# The GCI image has host firewall which drop most inbound/forwarded packets.
# We need to add rules to accept all TCP/UDP/ICMP packets.
if iptables -w -L INPUT | grep "Chain INPUT (policy DROP)" > /dev/null; then
echo "Add rules to accept all inbound TCP/UDP/ICMP packets"
iptables -A INPUT -w -p TCP -j ACCEPT
iptables -A INPUT -w -p UDP -j ACCEPT
iptables -A INPUT -w -p ICMP -j ACCEPT
fi
if iptables -w -L FORWARD | grep "Chain FORWARD (policy DROP)" > /dev/null; then
echo "Add rules to accept all forwarded TCP/UDP/ICMP packets"
iptables -A FORWARD -w -p TCP -j ACCEPT
iptables -A FORWARD -w -p UDP -j ACCEPT
iptables -A FORWARD -w -p ICMP -j ACCEPT
fi
`
	return &nodetasks.File{
		Path:     "/opt/kops/bin/iptables-setup",
		Contents: fi.NewStringResource(script),
		Type:     nodetasks.FileType_File,
		Mode:     s("0755"),
	}
}
