From 60ec6196dfc0da58f720071607bccdd7526347ce Mon Sep 17 00:00:00 2001 From: Francesco Cheinasso Date: Thu, 28 Sep 2023 12:32:11 +0200 Subject: [PATCH] Wireguard Tunnel Container --- .github/workflows/integration.yml | 2 + Makefile | 31 ++-- apis/networking/v1alpha1/connection_types.go | 5 + apis/networking/v1alpha1/publickey_types.go | 1 + build/gateway/tunnel/wireguard/Dockerfile | 20 +++ cmd/gateway/tunnel/wireguard/main.go | 151 ++++++++++++++++++ cmd/liqonet/gateway-operator.go | 2 +- .../crds/networking.liqo.io_connections.yaml | 17 +- .../liqo/files/liqo-gateway-ClusterRole.yaml | 49 +----- .../tunnel-operator/tunnel-operator.go | 2 +- pkg/gateway/common/labels/doc.go | 16 ++ pkg/gateway/common/labels/labels.go | 28 ++++ .../connection}/conncheck/common.go | 0 .../connection}/conncheck/conncheck.go | 0 .../connection}/conncheck/consts.go | 0 .../connection}/conncheck/doc.go | 0 .../connection}/conncheck/receiver.go | 0 .../connection}/conncheck/sender.go | 0 pkg/gateway/connection/k8s.go | 55 +++++++ pkg/gateway/tunnel/common/doc.go | 16 ++ pkg/gateway/tunnel/common/netlink.go | 32 ++++ pkg/gateway/tunnel/common/options.go | 46 ++++++ pkg/gateway/tunnel/wireguard/device.go | 58 +++++++ pkg/gateway/tunnel/wireguard/dns.go | 130 +++++++++++++++ pkg/gateway/tunnel/wireguard/doc.go | 16 ++ pkg/gateway/tunnel/wireguard/flags.go | 121 ++++++++++++++ pkg/gateway/tunnel/wireguard/k8s.go | 126 +++++++++++++++ pkg/gateway/tunnel/wireguard/keys.go | 58 +++++++ pkg/gateway/tunnel/wireguard/labels.go | 23 +++ pkg/gateway/tunnel/wireguard/netlink.go | 54 +++++++ pkg/gateway/tunnel/wireguard/options.go | 58 +++++++ .../tunnel/wireguard/publickeys-controller.go | 117 ++++++++++++++ pkg/liqonet/tunnel/driver.go | 2 +- pkg/liqonet/tunnel/wireguard/driver.go | 2 +- pkg/utils/getters/k8sGetters.go | 11 ++ 35 files changed, 1183 insertions(+), 66 deletions(-) create mode 100644 build/gateway/tunnel/wireguard/Dockerfile create mode 100644 cmd/gateway/tunnel/wireguard/main.go create mode 100644 pkg/gateway/common/labels/doc.go create mode 100644 pkg/gateway/common/labels/labels.go rename pkg/{liqonet => gateway/connection}/conncheck/common.go (100%) rename pkg/{liqonet => gateway/connection}/conncheck/conncheck.go (100%) rename pkg/{liqonet => gateway/connection}/conncheck/consts.go (100%) rename pkg/{liqonet => gateway/connection}/conncheck/doc.go (100%) rename pkg/{liqonet => gateway/connection}/conncheck/receiver.go (100%) rename pkg/{liqonet => gateway/connection}/conncheck/sender.go (100%) create mode 100644 pkg/gateway/connection/k8s.go create mode 100644 pkg/gateway/tunnel/common/doc.go create mode 100644 pkg/gateway/tunnel/common/netlink.go create mode 100644 pkg/gateway/tunnel/common/options.go create mode 100644 pkg/gateway/tunnel/wireguard/device.go create mode 100644 pkg/gateway/tunnel/wireguard/dns.go create mode 100644 pkg/gateway/tunnel/wireguard/doc.go create mode 100644 pkg/gateway/tunnel/wireguard/flags.go create mode 100644 pkg/gateway/tunnel/wireguard/k8s.go create mode 100644 pkg/gateway/tunnel/wireguard/keys.go create mode 100644 pkg/gateway/tunnel/wireguard/labels.go create mode 100644 pkg/gateway/tunnel/wireguard/netlink.go create mode 100644 pkg/gateway/tunnel/wireguard/options.go create mode 100644 pkg/gateway/tunnel/wireguard/publickeys-controller.go diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 75ea08626e..154725523c 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -8,6 +8,7 @@ on: - network-general - network-external - network-internal + - frc/wgtunnel repository_dispatch: types: - test-command @@ -90,6 +91,7 @@ jobs: - metric-agent - telemetry - proxy + - gateway/tunnel/wireguard steps: - name: Set up QEMU diff --git a/Makefile b/Makefile index d12e6e162b..79bb8e7e33 100644 --- a/Makefile +++ b/Makefile @@ -60,21 +60,22 @@ manifests: controller-gen #Generate RBAC for each controller rbacs: controller-gen rm -f deployments/liqo/files/* - $(CONTROLLER_GEN) paths="./internal/liqonet/route-operator" rbac:roleName=liqo-route output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-route-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && $(SED_COMMAND) deployments/liqo/files/liqo-route-ClusterRole.yaml deployments/liqo/files/liqo-route-Role.yaml - $(CONTROLLER_GEN) paths="./internal/liqonet/tunnel-operator" rbac:roleName=liqo-gateway output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-gateway-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && $(SED_COMMAND) deployments/liqo/files/liqo-gateway-ClusterRole.yaml deployments/liqo/files/liqo-gateway-Role.yaml - $(CONTROLLER_GEN) paths="./internal/liqonet/network-manager/..." rbac:roleName=liqo-network-manager output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-network-manager-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && $(SED_COMMAND) deployments/liqo/files/liqo-network-manager-ClusterRole.yaml deployments/liqo/files/liqo-network-manager-Role.yaml - $(CONTROLLER_GEN) paths="./internal/crdReplicator" rbac:roleName=liqo-crd-replicator output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-crd-replicator-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && $(SED_COMMAND) deployments/liqo/files/liqo-crd-replicator-ClusterRole.yaml deployments/liqo/files/liqo-crd-replicator-Role.yaml - $(CONTROLLER_GEN) paths="./pkg/discoverymanager" rbac:roleName=liqo-discovery output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-discovery-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && $(SED_COMMAND) deployments/liqo/files/liqo-discovery-ClusterRole.yaml deployments/liqo/files/liqo-discovery-Role.yaml - $(CONTROLLER_GEN) paths="./internal/auth-service" rbac:roleName=liqo-auth-service output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-auth-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && $(SED_COMMAND) deployments/liqo/files/liqo-auth-ClusterRole.yaml deployments/liqo/files/liqo-auth-Role.yaml - $(CONTROLLER_GEN) paths="./pkg/peering-roles/basic" rbac:roleName=liqo-remote-peering-basic output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-remote-peering-basic-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && $(SED_COMMAND) deployments/liqo/files/liqo-remote-peering-basic-ClusterRole.yaml - $(CONTROLLER_GEN) paths="./pkg/peering-roles/incoming" rbac:roleName=liqo-remote-peering-incoming output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-remote-peering-incoming-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && $(SED_COMMAND) deployments/liqo/files/liqo-remote-peering-incoming-ClusterRole.yaml - $(CONTROLLER_GEN) paths="./pkg/peering-roles/outgoing" rbac:roleName=liqo-remote-peering-outgoing output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-remote-peering-outgoing-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && $(SED_COMMAND) deployments/liqo/files/liqo-remote-peering-outgoing-ClusterRole.yaml - $(CONTROLLER_GEN) paths="./pkg/liqo-controller-manager/..." rbac:roleName=liqo-controller-manager output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-controller-manager-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && $(SED_COMMAND) deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml deployments/liqo/files/liqo-controller-manager-Role.yaml - $(CONTROLLER_GEN) paths="./pkg/virtualKubelet/roles/local" rbac:roleName=liqo-virtual-kubelet-local output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-virtual-kubelet-local-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && $(SED_COMMAND) deployments/liqo/files/liqo-virtual-kubelet-local-ClusterRole.yaml - $(CONTROLLER_GEN) paths="./pkg/virtualKubelet/roles/remote" rbac:roleName=liqo-virtual-kubelet-remote output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-virtual-kubelet-remote-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && $(SED_COMMAND) deployments/liqo/files/liqo-virtual-kubelet-remote-ClusterRole.yaml - $(CONTROLLER_GEN) paths="./cmd/uninstaller" rbac:roleName=liqo-pre-delete output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-pre-delete-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && $(SED_COMMAND) deployments/liqo/files/liqo-pre-delete-ClusterRole.yaml - $(CONTROLLER_GEN) paths="./cmd/metric-agent" rbac:roleName=liqo-metric-agent output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-metric-agent-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && $(SED_COMMAND) deployments/liqo/files/liqo-metric-agent-ClusterRole.yaml - $(CONTROLLER_GEN) paths="./cmd/telemetry" rbac:roleName=liqo-telemetry output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-telemetry-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && $(SED_COMMAND) deployments/liqo/files/liqo-telemetry-ClusterRole.yaml + $(CONTROLLER_GEN) paths="./internal/liqonet/route-operator" rbac:roleName=liqo-route output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-route-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && sed -i -n '/rules/,$$p' deployments/liqo/files/liqo-route-ClusterRole.yaml deployments/liqo/files/liqo-route-Role.yaml + $(CONTROLLER_GEN) paths="./internal/liqonet/tunnel-operator" rbac:roleName=liqo-gateway output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-gateway-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && sed -i -n '/rules/,$$p' deployments/liqo/files/liqo-gateway-ClusterRole.yaml deployments/liqo/files/liqo-gateway-Role.yaml + $(CONTROLLER_GEN) paths="./internal/liqonet/network-manager/..." rbac:roleName=liqo-network-manager output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-network-manager-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && sed -i -n '/rules/,$$p' deployments/liqo/files/liqo-network-manager-ClusterRole.yaml deployments/liqo/files/liqo-network-manager-Role.yaml + $(CONTROLLER_GEN) paths="./internal/crdReplicator" rbac:roleName=liqo-crd-replicator output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-crd-replicator-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && sed -i -n '/rules/,$$p' deployments/liqo/files/liqo-crd-replicator-ClusterRole.yaml deployments/liqo/files/liqo-crd-replicator-Role.yaml + $(CONTROLLER_GEN) paths="./pkg/discoverymanager" rbac:roleName=liqo-discovery output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-discovery-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && sed -i -n '/rules/,$$p' deployments/liqo/files/liqo-discovery-ClusterRole.yaml deployments/liqo/files/liqo-discovery-Role.yaml + $(CONTROLLER_GEN) paths="./internal/auth-service" rbac:roleName=liqo-auth-service output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-auth-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && sed -i -n '/rules/,$$p' deployments/liqo/files/liqo-auth-ClusterRole.yaml deployments/liqo/files/liqo-auth-Role.yaml + $(CONTROLLER_GEN) paths="./pkg/peering-roles/basic" rbac:roleName=liqo-remote-peering-basic output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-remote-peering-basic-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && sed -i -n '/rules/,$$p' deployments/liqo/files/liqo-remote-peering-basic-ClusterRole.yaml + $(CONTROLLER_GEN) paths="./pkg/peering-roles/incoming" rbac:roleName=liqo-remote-peering-incoming output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-remote-peering-incoming-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && sed -i -n '/rules/,$$p' deployments/liqo/files/liqo-remote-peering-incoming-ClusterRole.yaml + $(CONTROLLER_GEN) paths="./pkg/peering-roles/outgoing" rbac:roleName=liqo-remote-peering-outgoing output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-remote-peering-outgoing-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && sed -i -n '/rules/,$$p' deployments/liqo/files/liqo-remote-peering-outgoing-ClusterRole.yaml + $(CONTROLLER_GEN) paths="./pkg/liqo-controller-manager/..." rbac:roleName=liqo-controller-manager output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-controller-manager-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && sed -i -n '/rules/,$$p' deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml deployments/liqo/files/liqo-controller-manager-Role.yaml + $(CONTROLLER_GEN) paths="./pkg/virtualKubelet/roles/local" rbac:roleName=liqo-virtual-kubelet-local output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-virtual-kubelet-local-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && sed -i -n '/rules/,$$p' deployments/liqo/files/liqo-virtual-kubelet-local-ClusterRole.yaml + $(CONTROLLER_GEN) paths="./pkg/virtualKubelet/roles/remote" rbac:roleName=liqo-virtual-kubelet-remote output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-virtual-kubelet-remote-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && sed -i -n '/rules/,$$p' deployments/liqo/files/liqo-virtual-kubelet-remote-ClusterRole.yaml + $(CONTROLLER_GEN) paths="./cmd/uninstaller" rbac:roleName=liqo-pre-delete output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-pre-delete-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && sed -i -n '/rules/,$$p' deployments/liqo/files/liqo-pre-delete-ClusterRole.yaml + $(CONTROLLER_GEN) paths="./cmd/metric-agent" rbac:roleName=liqo-metric-agent output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-metric-agent-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && sed -i -n '/rules/,$$p' deployments/liqo/files/liqo-metric-agent-ClusterRole.yaml + $(CONTROLLER_GEN) paths="./cmd/telemetry" rbac:roleName=liqo-telemetry output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-telemetry-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && sed -i -n '/rules/,$$p' deployments/liqo/files/liqo-telemetry-ClusterRole.yaml + $(CONTROLLER_GEN) paths="./pkg/gateway/..." rbac:roleName=liqo-gateway output:rbac:stdout | awk -v RS="---\n" 'NR>1{f="./deployments/liqo/files/liqo-gateway-" $$4 ".yaml";printf "%s",$$0 > f; close(f)}' && sed -i -n '/rules/,$$p' deployments/liqo/files/liqo-gateway-ClusterRole.yaml # Install gci if not available gci: diff --git a/apis/networking/v1alpha1/connection_types.go b/apis/networking/v1alpha1/connection_types.go index 3b9d5cdbc1..22a139cfd6 100644 --- a/apis/networking/v1alpha1/connection_types.go +++ b/apis/networking/v1alpha1/connection_types.go @@ -117,6 +117,11 @@ type ConnectionStatus struct { // +kubebuilder:object:root=true // +kubebuilder:resource:categories=liqo // +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type` +// +kubebuilder:printcolumn:name="GatewayName",type=string,JSONPath=`.status.gatewayRef.name`,priority=1 +// +kubebuilder:printcolumn:name="GatewayKind",type=string,JSONPath=`.status.gatewayRef.kind`,priority=1 +// TODO: add the status of the ping, this is not possible now because the ping is not implemented yet. +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` // Connection contains the status of a connection between two clusters (a client and a server). type Connection struct { diff --git a/apis/networking/v1alpha1/publickey_types.go b/apis/networking/v1alpha1/publickey_types.go index c6055fbbc5..c1f1765f9d 100644 --- a/apis/networking/v1alpha1/publickey_types.go +++ b/apis/networking/v1alpha1/publickey_types.go @@ -45,6 +45,7 @@ type PublicKeySpec struct { // +kubebuilder:object:root=true // +kubebuilder:resource:categories=liqo,path=publickeies +// +kubebuilder:column:name=publicKey,JSONPath=".spec.publicKey",type=string // PublicKey contains a public key data required by some interconnection technologies. type PublicKey struct { diff --git a/build/gateway/tunnel/wireguard/Dockerfile b/build/gateway/tunnel/wireguard/Dockerfile new file mode 100644 index 0000000000..d956845ea7 --- /dev/null +++ b/build/gateway/tunnel/wireguard/Dockerfile @@ -0,0 +1,20 @@ +FROM golang:1.21 as goBuilder +WORKDIR /tmp/builder + +COPY go.mod ./go.mod +COPY go.sum ./go.sum +RUN go mod download + +COPY . ./ +RUN CGO_ENABLED=0 GOOS=linux GOARCH=$(go env GOARCH) go build -ldflags="-s -w" ./cmd/gateway/tunnel/wireguard + + +FROM alpine:3.18 + +RUN apk update && \ + apk add iptables bash wireguard-tools tcpdump conntrack-tools curl && \ + rm -rf /var/cache/apk/* + +COPY --from=goBuilder /tmp/builder/wireguard /usr/bin/liqo-wireguard + +CMD [ "/usr/bin/wireguard" ] diff --git a/cmd/gateway/tunnel/wireguard/main.go b/cmd/gateway/tunnel/wireguard/main.go new file mode 100644 index 0000000000..ce334d4f6d --- /dev/null +++ b/cmd/gateway/tunnel/wireguard/main.go @@ -0,0 +1,151 @@ +// Copyright 2019-2023 The Liqo 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 wireguard contains the logic to configure the Wireguard interface. +package main + +import ( + "flag" + "net" + "os" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/leaderelection/resourcelock" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/log" + + ipamv1alpha1 "github.com/liqotech/liqo/apis/ipam/v1alpha1" + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" + "github.com/liqotech/liqo/pkg/gateway/tunnel/wireguard" + "github.com/liqotech/liqo/pkg/utils/mapper" + "github.com/liqotech/liqo/pkg/utils/restcfg" +) + +var ( + addToSchemeFunctions = []func(*runtime.Scheme) error{ + corev1.AddToScheme, + networkingv1alpha1.AddToScheme, + ipamv1alpha1.AddToScheme, + } +) + +func main() { + var err error + ctx := ctrl.SetupSignalHandler() + options := wireguard.NewOptions() + scheme := runtime.NewScheme() + + // Init flags and check mandatory ones. + restcfg.InitFlags(nil) + klog.InitFlags(nil) + wireguard.InitFlags(options) + flag.Parse() + if err = wireguard.CheckMandatoryFlags(options); err != nil { + klog.Errorf("Mandatory flags: %v", err) + os.Exit(1) + } + + // Adds the APIs to the scheme. + for _, addToScheme := range addToSchemeFunctions { + if err = addToScheme(scheme); err != nil { + klog.Errorf("unable to add scheme: %v", err) + os.Exit(1) + } + } + + // Set controller-runtime logger. + log.SetLogger(klog.NewKlogr()) + + // Get the rest config. + cfg := config.GetConfigOrDie() + + // Create the client. This cliend should be used only outside the reconciler. + cl, err := client.New(cfg, client.Options{ + Scheme: scheme, + Cache: nil, + }) + if err != nil { + klog.Errorf("unable to create client: %v", err) + os.Exit(1) + } + + // Create the manager. + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + MapperProvider: mapper.LiqoMapperProvider(scheme), + Scheme: scheme, + Namespace: options.Namespace, + MetricsBindAddress: options.MetricsAddress, + HealthProbeBindAddress: options.ProbeAddr, + LeaderElection: options.LeaderElection, + LeaderElectionID: "66cf253f.wgtunnel.liqo.io", + LeaderElectionNamespace: options.Namespace, + LeaderElectionReleaseOnCancel: true, + LeaderElectionResourceLock: resourcelock.LeasesResourceLock, + }) + if err != nil { + klog.Error(err) + os.Exit(1) + } + + // Setup the controller. + pkr, err := wireguard.NewPublicKeysReconciler( + mgr.GetClient(), + mgr.GetScheme(), + mgr.GetEventRecorderFor("wireguard-controller"), + options, + ) + if err != nil { + klog.Error(err) + os.Exit(1) + } + + dnsChan := make(chan event.GenericEvent) + if wireguard.IsDNSRoutineRequired(options) { + go wireguard.StartDNSRoutine(ctx, dnsChan, options) + klog.Infof("Starting DNS routine: resolving the endpoint address every %s", options.DNSCheckInterval.String()) + } else { + options.EndpointIP = net.ParseIP(options.EndpointAddress) + klog.Infof("Setting static endpoint IP: %s", options.EndpointIP) + } + + // Setup the controller. + if err = pkr.SetupWithManager(mgr, dnsChan); err != nil { + klog.Error(err) + os.Exit(1) + } + + // Ensure presence of Secret with private and public keys. + if err = wireguard.EnsureKeysSecret(ctx, cl, options); err != nil { + klog.Error(err) + os.Exit(1) + } + + // Create the wg-liqo interface and init the wireguard configuration depending on the mode (client/server). + err = wireguard.InitWireguardLink(options) + if err != nil { + klog.Errorf("unable to create wireguard interface: %v", err) + os.Exit(1) + } + + // Start the manager. + if err = mgr.Start(ctx); err != nil { + klog.Error(err) + os.Exit(1) + } +} diff --git a/cmd/liqonet/gateway-operator.go b/cmd/liqonet/gateway-operator.go index 468574e893..b416fde7b7 100644 --- a/cmd/liqonet/gateway-operator.go +++ b/cmd/liqonet/gateway-operator.go @@ -34,7 +34,7 @@ import ( tunneloperator "github.com/liqotech/liqo/internal/liqonet/tunnel-operator" liqoconst "github.com/liqotech/liqo/pkg/consts" - "github.com/liqotech/liqo/pkg/liqonet/conncheck" + "github.com/liqotech/liqo/pkg/gateway/connection/conncheck" liqonetns "github.com/liqotech/liqo/pkg/liqonet/netns" liqonetutils "github.com/liqotech/liqo/pkg/liqonet/utils" "github.com/liqotech/liqo/pkg/liqonet/utils/links" diff --git a/deployments/liqo/charts/liqo-crds/crds/networking.liqo.io_connections.yaml b/deployments/liqo/charts/liqo-crds/crds/networking.liqo.io_connections.yaml index 48c7509a63..3c17fca18e 100644 --- a/deployments/liqo/charts/liqo-crds/crds/networking.liqo.io_connections.yaml +++ b/deployments/liqo/charts/liqo-crds/crds/networking.liqo.io_connections.yaml @@ -16,7 +16,22 @@ spec: singular: connection scope: Namespaced versions: - - name: v1alpha1 + - additionalPrinterColumns: + - jsonPath: .spec.type + name: Type + type: string + - jsonPath: .status.gatewayRef.name + name: GatewayName + priority: 1 + type: string + - jsonPath: .status.gatewayRef.kind + name: GatewayKind + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 schema: openAPIV3Schema: description: Connection contains the status of a connection between two clusters diff --git a/deployments/liqo/files/liqo-gateway-ClusterRole.yaml b/deployments/liqo/files/liqo-gateway-ClusterRole.yaml index 1cbb3a349e..61aa35e3fd 100644 --- a/deployments/liqo/files/liqo-gateway-ClusterRole.yaml +++ b/deployments/liqo/files/liqo-gateway-ClusterRole.yaml @@ -2,63 +2,20 @@ rules: - apiGroups: - "" resources: - - events + - secrets verbs: - create - delete - get - list - - patch - update - - watch - apiGroups: - - "" - resources: - - pods - verbs: - - get - - list - - patch - - update - - watch -- apiGroups: - - "" - resources: - - services - verbs: - - list - - patch - - update - - watch -- apiGroups: - - net.liqo.io + - networking.liqo.io resources: - - natmappings + - publickeys verbs: - create - delete - get - list - - patch - - update - - watch -- apiGroups: - - net.liqo.io - resources: - - tunnelendpoints - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - net.liqo.io - resources: - - tunnelendpoints/status - verbs: - - get - - patch - update diff --git a/internal/liqonet/tunnel-operator/tunnel-operator.go b/internal/liqonet/tunnel-operator/tunnel-operator.go index c8347a7724..ba97faec12 100644 --- a/internal/liqonet/tunnel-operator/tunnel-operator.go +++ b/internal/liqonet/tunnel-operator/tunnel-operator.go @@ -41,7 +41,7 @@ import ( netv1alpha1 "github.com/liqotech/liqo/apis/net/v1alpha1" liqoconst "github.com/liqotech/liqo/pkg/consts" - "github.com/liqotech/liqo/pkg/liqonet/conncheck" + "github.com/liqotech/liqo/pkg/gateway/connection/conncheck" "github.com/liqotech/liqo/pkg/liqonet/iptables" liqonetns "github.com/liqotech/liqo/pkg/liqonet/netns" liqorouting "github.com/liqotech/liqo/pkg/liqonet/routing" diff --git a/pkg/gateway/common/labels/doc.go b/pkg/gateway/common/labels/doc.go new file mode 100644 index 0000000000..ae82ee47a3 --- /dev/null +++ b/pkg/gateway/common/labels/doc.go @@ -0,0 +1,16 @@ +// Copyright 2019-2023 The Liqo 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 labels contains the labels used by the Gateway. +package labels diff --git a/pkg/gateway/common/labels/labels.go b/pkg/gateway/common/labels/labels.go new file mode 100644 index 0000000000..710e9aecc4 --- /dev/null +++ b/pkg/gateway/common/labels/labels.go @@ -0,0 +1,28 @@ +// Copyright 2019-2023 The Liqo 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 labels + +// Labels is the type used to identify the wireguard labels. +type Labels string + +const ( + // LabelsRemoteClusterID is the label used to identify the remote cluster ID. + LabelsRemoteClusterID Labels = "networking.liqo.io/remote-cluster-id" + + // LabelsSecret is the label used to identify the wireguard secret. + LabelsSecret Labels = "networking.liqo.io/gateway-secret" + // LabelsSecretValue is the value of the wireguard secret label. + LabelsSecretValue Labels = "true" +) diff --git a/pkg/liqonet/conncheck/common.go b/pkg/gateway/connection/conncheck/common.go similarity index 100% rename from pkg/liqonet/conncheck/common.go rename to pkg/gateway/connection/conncheck/common.go diff --git a/pkg/liqonet/conncheck/conncheck.go b/pkg/gateway/connection/conncheck/conncheck.go similarity index 100% rename from pkg/liqonet/conncheck/conncheck.go rename to pkg/gateway/connection/conncheck/conncheck.go diff --git a/pkg/liqonet/conncheck/consts.go b/pkg/gateway/connection/conncheck/consts.go similarity index 100% rename from pkg/liqonet/conncheck/consts.go rename to pkg/gateway/connection/conncheck/consts.go diff --git a/pkg/liqonet/conncheck/doc.go b/pkg/gateway/connection/conncheck/doc.go similarity index 100% rename from pkg/liqonet/conncheck/doc.go rename to pkg/gateway/connection/conncheck/doc.go diff --git a/pkg/liqonet/conncheck/receiver.go b/pkg/gateway/connection/conncheck/receiver.go similarity index 100% rename from pkg/liqonet/conncheck/receiver.go rename to pkg/gateway/connection/conncheck/receiver.go diff --git a/pkg/liqonet/conncheck/sender.go b/pkg/gateway/connection/conncheck/sender.go similarity index 100% rename from pkg/liqonet/conncheck/sender.go rename to pkg/gateway/connection/conncheck/sender.go diff --git a/pkg/gateway/connection/k8s.go b/pkg/gateway/connection/k8s.go new file mode 100644 index 0000000000..e967562067 --- /dev/null +++ b/pkg/gateway/connection/k8s.go @@ -0,0 +1,55 @@ +// Copyright 2019-2023 The Liqo 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 connection + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" +) + +// forgeConnectionStatus forges the status of the connection. +func forgeConnectionStatus(conn *networkingv1alpha1.Connection, + condType *networkingv1alpha1.ConnectionConditionType, + condStatus *networkingv1alpha1.ConnectionConditionStatusType) (update bool) { + update = false + for i := range conn.Status.Conditions { + if conn.Status.Conditions[i].Type == *condType { + if conn.Status.Conditions[i].Status != *condStatus { + update = true + conn.Status.Conditions[i].Status = *condStatus + conn.Status.Conditions[i].Message = "" + conn.Status.Conditions[i].Reason = "" + conn.Status.Conditions[i].LastTransitionTime = metav1.Now() + } + return update + } + } + return update +} + +// UpdateConnectionStatus updates the status of the connection. +func UpdateConnectionStatus(ctx context.Context, cl client.Client, conn *networkingv1alpha1.Connection, + condType *networkingv1alpha1.ConnectionConditionType, + condStatus *networkingv1alpha1.ConnectionConditionStatusType) error { + update := forgeConnectionStatus(conn, condType, condStatus) + if !update { + return nil + } + return cl.Status().Update(ctx, conn) +} diff --git a/pkg/gateway/tunnel/common/doc.go b/pkg/gateway/tunnel/common/doc.go new file mode 100644 index 0000000000..8b5f93cd07 --- /dev/null +++ b/pkg/gateway/tunnel/common/doc.go @@ -0,0 +1,16 @@ +// Copyright 2019-2023 The Liqo 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 common contains the common functions used by the tunneling package. +package common diff --git a/pkg/gateway/tunnel/common/netlink.go b/pkg/gateway/tunnel/common/netlink.go new file mode 100644 index 0000000000..f784928059 --- /dev/null +++ b/pkg/gateway/tunnel/common/netlink.go @@ -0,0 +1,32 @@ +// Copyright 2019-2023 The Liqo 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 common + +import "github.com/vishvananda/netlink" + +// AddAddress adds an IP address to the Wireguard interface. +func AddAddress(link netlink.Link, ip string) error { + addr, err := netlink.ParseAddr(ip) + if err != nil { + return err + } + + return netlink.AddrAdd(link, addr) +} + +// GetLink returns the Wireguard interface. +func GetLink(name string) (netlink.Link, error) { + return netlink.LinkByName(name) +} diff --git a/pkg/gateway/tunnel/common/options.go b/pkg/gateway/tunnel/common/options.go new file mode 100644 index 0000000000..e3ddfdb5b0 --- /dev/null +++ b/pkg/gateway/tunnel/common/options.go @@ -0,0 +1,46 @@ +// Copyright 2019-2023 The Liqo 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 common + +import ( + "fmt" +) + +// Mode is the mode in which the wireguard interface is configured. +type Mode string + +const ( + // ModeServer is the mode in which the wireguard interface is configured as a server. + ModeServer Mode = "server" + // ModeClient is the mode in which the wireguard interface is configured as a client. + ModeClient Mode = "client" +) + +// String returns the string representation of the mode. +func (m Mode) String() string { + return string(m) +} + +// Set sets the value of the mode. +func (m *Mode) Set(value string) error { + if value == "" { + return fmt.Errorf("mode cannot be empty") + } + if value != ModeServer.String() && value != ModeClient.String() { + return fmt.Errorf("invalid mode %q", value) + } + *m = Mode(value) + return nil +} diff --git a/pkg/gateway/tunnel/wireguard/device.go b/pkg/gateway/tunnel/wireguard/device.go new file mode 100644 index 0000000000..fc1c9f9d26 --- /dev/null +++ b/pkg/gateway/tunnel/wireguard/device.go @@ -0,0 +1,58 @@ +// Copyright 2019-2023 The Liqo 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 wireguard + +import ( + "net" + + "golang.zx2c4.com/wireguard/wgctrl" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +func configureServerDevice(wgcl *wgctrl.Client, options *Options, peerPubKey wgtypes.Key) error { + return wgcl.ConfigureDevice(options.InterfaceName, wgtypes.Config{ + PrivateKey: &options.PrivateKey, + ListenPort: &options.ListenPort, + Peers: []wgtypes.PeerConfig{ + { + PublicKey: peerPubKey, + AllowedIPs: []net.IPNet{{IP: net.IP{0, 0, 0, 0}, Mask: net.CIDRMask(0, 32)}}, + }, + }, + ReplacePeers: true, + }) +} + +// Runs in a goroutine. +func configureClientDevice(wgcl *wgctrl.Client, options *Options, peerPubKey wgtypes.Key) error { + options.EndpointIPMutex.Lock() + defer options.EndpointIPMutex.Unlock() + + return wgcl.ConfigureDevice(options.InterfaceName, wgtypes.Config{ + PrivateKey: &options.PrivateKey, + ListenPort: nil, + Peers: []wgtypes.PeerConfig{ + { + PublicKey: peerPubKey, + AllowedIPs: []net.IPNet{{IP: net.IP{0, 0, 0, 0}, Mask: net.CIDRMask(0, 32)}}, + Endpoint: &net.UDPAddr{ + IP: options.EndpointIP, + Port: options.EndpointPort, + }, + }, + }, + ReplacePeers: true, + }) +} diff --git a/pkg/gateway/tunnel/wireguard/dns.go b/pkg/gateway/tunnel/wireguard/dns.go new file mode 100644 index 0000000000..876531e9ff --- /dev/null +++ b/pkg/gateway/tunnel/wireguard/dns.go @@ -0,0 +1,130 @@ +// Copyright 2019-2023 The Liqo 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 wireguard + +import ( + "context" + "errors" + "net" + "os" + + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + + gwlabels "github.com/liqotech/liqo/pkg/gateway/common/labels" + "github.com/liqotech/liqo/pkg/gateway/tunnel/common" + "github.com/liqotech/liqo/pkg/utils/getters" +) + +// StartDNSRoutine run a routine which periodically resolves the DNS associated to the wireguard client endpoint. +// The DNS is resolved every 5 minutes. +// If the DNS changed a new publickkeys-controller reconcile is triggered through a generic event. +func StartDNSRoutine(ctx context.Context, ch chan event.GenericEvent, opts *Options) { + err := wait.PollUntilContextCancel(ctx, opts.DNSCheckInterval, true, func(ctx context.Context) (done bool, err error) { + ips, err := net.LookupIP(opts.EndpointAddress) + if err != nil { + dnsErr := &net.DNSError{} + if !errors.As(err, &dnsErr) { + return false, err + } + switch { + case dnsErr.IsNotFound: + klog.Warningf("DNS %q not found", opts.EndpointAddress) + return false, nil + case dnsErr.IsTimeout: + klog.Warningf("DNS %q timeout", opts.EndpointAddress) + return false, nil + default: + return false, err + } + } + + // Checks if the DNS resolution has changed + for _, ip := range ips { + if opts.EndpointIP.Equal(ip) { + return false, nil + } + } + + klog.Infof("DNS %q resolved to %q: updating endpoint", opts.EndpointAddress, ips[0]) + + // Copies the new IPs to store for the next check + opts.EndpointIPMutex.Lock() + defer opts.EndpointIPMutex.Unlock() + opts.EndpointIP = ips[0] + + // Triggers a new reconcile + ch <- event.GenericEvent{} + return false, nil + }) + if err != nil { + klog.Error(err) + os.Exit(1) + } +} + +// IsDNSRoutineRequired checks if the client endpoint is a DNS. +// If it is a DNS the DNS routine is required. +func IsDNSRoutineRequired(opts *Options) bool { + if opts.Mode != common.ModeClient { + return false + } + return net.ParseIP(opts.EndpointAddress) == nil +} + +// WatcherDNS is a structure containing Source and EventHandler +// for controller-manager WatchesRawSources function. +type WatcherDNS struct { + Source source.Channel + EventHandler handler.EventHandler +} + +// NewWatcherDNS creates a new WatcherDNS. +func NewWatcherDNS(src <-chan event.GenericEvent, cl client.Client, opts *Options) *WatcherDNS { + return &WatcherDNS{ + Source: source.Channel{ + Source: src, + }, + EventHandler: handler.EnqueueRequestsFromMapFunc( + func(ctx context.Context, _ client.Object) []reconcile.Request { + labelSet := labels.Set{ + string(LabelsMode): string(opts.Mode), + string(gwlabels.LabelsRemoteClusterID): opts.RemoteClusterID, + } + list, err := getters.ListPublicKeysByLabel(ctx, cl, opts.Namespace, labels.SelectorFromSet(labelSet)) + if err != nil { + klog.Error(err) + } + if len(list.Items) == 0 { + klog.Errorf("There are no public keys with label %s", labelSet) + return nil + } + if len(list.Items) != 1 { + klog.Errorf("There are %d public keys with label %s", len(list.Items), labelSet) + return nil + } + return []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: list.Items[0].Name, Namespace: list.Items[0].Namespace}}, + } + }), + } +} diff --git a/pkg/gateway/tunnel/wireguard/doc.go b/pkg/gateway/tunnel/wireguard/doc.go new file mode 100644 index 0000000000..81baec8e31 --- /dev/null +++ b/pkg/gateway/tunnel/wireguard/doc.go @@ -0,0 +1,16 @@ +// Copyright 2019-2023 The Liqo 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 wireguard contains the implementation of the wireguard tunnel. +package wireguard diff --git a/pkg/gateway/tunnel/wireguard/flags.go b/pkg/gateway/tunnel/wireguard/flags.go new file mode 100644 index 0000000000..bea9f8704f --- /dev/null +++ b/pkg/gateway/tunnel/wireguard/flags.go @@ -0,0 +1,121 @@ +// Copyright 2019-2023 The Liqo 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 wireguard + +import ( + "flag" + "fmt" + "time" + + "github.com/liqotech/liqo/pkg/gateway/tunnel/common" +) + +// FlagName is the type for the name of the flags. +type FlagName string + +func (fn FlagName) String() string { + return string(fn) +} + +const ( + // FlagNameName is the name of the WgGateway resource. + FlagNameName FlagName = "name" + // FlagNameNamespace is the namespace WgGateway resource. + FlagNameNamespace FlagName = "namespace" + // FlagNameRemoteClusterID is the clusterID of the remote cluster. + FlagNameRemoteClusterID FlagName = "remote-cluster-id" + // FlagNameWireguardGatewayUID is the UID of the wireguard gateway. + FlagNameWireguardGatewayUID FlagName = "wireguard-gateway-uid" + + // FlagNameMode is the mode in which the wireguard interface is configured. + FlagNameMode FlagName = "mode" + // FlagNameMTU is the MTU for the wireguard interface. + FlagNameMTU FlagName = "mtu" + // FlagNameListenPort is the listen port for the wireguard interface. + FlagNameListenPort FlagName = "listen-port" + // FlagNameInterfaceName is the name of the wireguard interface. + FlagNameInterfaceName FlagName = "interface-name" + // FlagNameInterfaceIP is the IP of the wireguard interface. + FlagNameInterfaceIP FlagName = "interface-ip" + // FlagNameEndpointAddress is the address of the endpoint for the wireguard interface. + FlagNameEndpointAddress FlagName = "endpoint-address" + // FlagNameEndpointPort is the port of the endpoint for the wireguard interface. + FlagNameEndpointPort FlagName = "endpoint-port" + + // FlagNameDNSCheckInterval is the interval between two DNS checks. + FlagNameDNSCheckInterval FlagName = "dns-check-interval" + + // FlagNameMetricsAddress is the address for the metrics endpoint. + FlagNameMetricsAddress FlagName = "metrics-address" + // FlagNameLeaderElection is the flag to enable leader election. + FlagNameLeaderElection FlagName = "leader-election" + // FlagNameProbeAddr is the address for the health probe endpoint. + FlagNameProbeAddr FlagName = "health-probe-bind-address" +) + +// MandatoryFlags contains the list of the mandatory flags. +var MandatoryFlags = []FlagName{ + FlagNameName, + FlagNameNamespace, + FlagNameRemoteClusterID, + FlagNameWireguardGatewayUID, + FlagNameMode, +} + +// ClientMandatoryFlags contains the list of the mandatory flags for the client mode. +var ClientMandatoryFlags = []FlagName{ + FlagNameEndpointAddress, +} + +// InitFlags initializes the flags for the wireguard tunnel. +func InitFlags(options *Options) { + flag.StringVar(&options.Name, FlagNameName.String(), "", "Name for the wireguard interface") + flag.StringVar(&options.Namespace, FlagNameNamespace.String(), "", "Namespace for the wireguard interface") + flag.StringVar(&options.RemoteClusterID, FlagNameRemoteClusterID.String(), "", "ClusterID for the wireguard interface") + flag.StringVar(&options.WireguardGatewayUID, FlagNameWireguardGatewayUID.String(), "", "WireguardGateway resource UID, used for owner reference") + + flag.Var(&options.Mode, FlagNameMode.String(), "Mode for the wireguard interface") + flag.IntVar(&options.MTU, FlagNameMTU.String(), 1420, "MTU for the wireguard interface") + flag.StringVar(&options.InterfaceName, FlagNameInterfaceName.String(), "liqo-wg", "Name for the wireguard interface") + flag.StringVar(&options.InterfaceIP, FlagNameInterfaceIP.String(), "169.254.0.1/30", "IP for the wireguard interface") + flag.IntVar(&options.ListenPort, FlagNameListenPort.String(), 51820, "Listen port for the wireguard interface (server only)") + flag.StringVar(&options.EndpointAddress, FlagNameEndpointAddress.String(), "", "Endpoint address for the wireguard peer (client only)") + flag.IntVar(&options.EndpointPort, FlagNameEndpointPort.String(), 51820, "Endpoint port for the wireguard peer (client only)") + + flag.DurationVar(&options.DNSCheckInterval, FlagNameDNSCheckInterval.String(), 5*time.Minute, "Interval between two DNS checks") + + flag.StringVar(&options.MetricsAddress, FlagNameMetricsAddress.String(), ":8080", "Address for the metrics endpoint") + flag.BoolVar(&options.LeaderElection, FlagNameLeaderElection.String(), false, "Enable leader election") + flag.StringVar(&options.ProbeAddr, FlagNameProbeAddr.String(), ":8081", "Address for the health probe endpoint") +} + +// CheckMandatoryFlags checks if the mandatory flags are set. +// This function must be called after flag.Parse(). +func CheckMandatoryFlags(options *Options) error { + flags := MandatoryFlags + if options.Mode == common.ModeClient { + flags = append(flags, ClientMandatoryFlags...) + } + for _, fn := range flags { + f := flag.Lookup(fn.String()) + if f == nil { + return fmt.Errorf("flag %s not found", fn) + } + if f.Value.String() == "" { + return fmt.Errorf("flag %s cannot be empty", fn) + } + } + return nil +} diff --git a/pkg/gateway/tunnel/wireguard/k8s.go b/pkg/gateway/tunnel/wireguard/k8s.go new file mode 100644 index 0000000000..bc94f7ebce --- /dev/null +++ b/pkg/gateway/tunnel/wireguard/k8s.go @@ -0,0 +1,126 @@ +// Copyright 2019-2023 The Liqo 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 wireguard + +import ( + "context" + "fmt" + + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" + commonlabels "github.com/liqotech/liqo/pkg/gateway/common/labels" + "github.com/liqotech/liqo/pkg/gateway/tunnel/common" +) + +// SetOwnerReferenceWithMode sets the owner reference of the object according to the mode. +func SetOwnerReferenceWithMode(opts *Options, obj metav1.Object, scheme *runtime.Scheme) error { + meta := metav1.ObjectMeta{ + Name: opts.Name, + Namespace: opts.Namespace, + UID: types.UID(opts.WireguardGatewayUID), + } + switch opts.Mode { + case common.ModeServer: + return controllerutil.SetOwnerReference(&networkingv1alpha1.WgGatewayServer{ObjectMeta: meta}, obj, scheme) + case common.ModeClient: + return controllerutil.SetOwnerReference(&networkingv1alpha1.WgGatewayClient{ObjectMeta: meta}, obj, scheme) + } + return fmt.Errorf("invalid mode %v", opts.Mode) +} + +// CheckKeysSecret checks if the keys secret exists and if it contains the private and public keys. +func CheckKeysSecret(ctx context.Context, cl client.Client, opts *Options) (wgtypes.Key, error) { + secret := &corev1.Secret{} + if err := cl.Get(ctx, types.NamespacedName{Name: opts.Name, Namespace: opts.Namespace}, secret); err != nil { + return wgtypes.Key{}, err + } + if secret.Data == nil { + return wgtypes.Key{}, nil + } + if k, ok := secret.Data[SecretPrivateKeyField]; !ok || len(k) != wgtypes.KeyLen { + return wgtypes.Key{}, nil + } + if k, ok := secret.Data[SecretPublicKeyField]; !ok || len(k) != wgtypes.KeyLen { + return wgtypes.Key{}, nil + } + return wgtypes.Key(secret.Data[SecretPrivateKeyField]), nil +} + +// CreateKeysSecret creates the private and public keys for the Wireguard interface and save them inside a Secret resource. +func CreateKeysSecret(ctx context.Context, cl client.Client, opts *Options, pri, pub wgtypes.Key) error { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: opts.Name, + Namespace: opts.Namespace, + }, + } + + if _, err := controllerutil.CreateOrUpdate(ctx, cl, secret, func() error { + secret.SetLabels(map[string]string{ + string(commonlabels.LabelsRemoteClusterID): opts.RemoteClusterID, + string(commonlabels.LabelsSecret): string(commonlabels.LabelsSecretValue), + }) + // TODO: uncomment this line when the owner reference will exist. + if err := SetOwnerReferenceWithMode(opts, secret, cl.Scheme()); err != nil { + return err + } + secret.Data = map[string][]byte{ + SecretPrivateKeyField: pri[:], + SecretPublicKeyField: pub[:], + } + return nil + }); err != nil { + return err + } + + return nil +} + +// EnsureConnection creates or updates the connection resource. +func (r *PublicKeysReconciler) EnsureConnection(ctx context.Context) error { + conn := &networkingv1alpha1.Connection{ObjectMeta: metav1.ObjectMeta{ + Name: r.Options.Name, Namespace: r.Options.Namespace, + }} + _, err := controllerutil.CreateOrUpdate(ctx, r.Client, conn, func() error { + // TODO: set the connection + if err := SetOwnerReferenceWithMode(r.Options, conn, r.Scheme); err != nil { + return err + } + conn.Spec.GatewayRef.APIVersion = networkingv1alpha1.GroupVersion.String() + conn.Spec.GatewayRef.Name = r.Options.Name + conn.Spec.GatewayRef.Namespace = r.Options.Namespace + conn.Spec.GatewayRef.UID = types.UID(r.Options.WireguardGatewayUID) + switch r.Options.Mode { + case common.ModeServer: + conn.Spec.Type = networkingv1alpha1.ConnectionTypeServer + conn.Spec.GatewayRef.Kind = networkingv1alpha1.WgGatewayServerKind + case common.ModeClient: + conn.Spec.Type = networkingv1alpha1.ConnectionTypeClient + conn.Spec.GatewayRef.Kind = networkingv1alpha1.WgGatewayClientKind + } + // We need to create an IP resource, maybe it' better to do it in the connection controller. + conn.Spec.Ping.Endpoint = networkingv1alpha1.EndpointStatus{} + return nil + }) + + return err +} diff --git a/pkg/gateway/tunnel/wireguard/keys.go b/pkg/gateway/tunnel/wireguard/keys.go new file mode 100644 index 0000000000..7f1ecb7465 --- /dev/null +++ b/pkg/gateway/tunnel/wireguard/keys.go @@ -0,0 +1,58 @@ +// Copyright 2019-2023 The Liqo 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 wireguard + +import ( + "context" + + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + kerrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// cluster-role +// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;create;delete;update + +const ( + // SecretPrivateKeyField is the name of the field inside the Secret resource that contains the private key. + SecretPrivateKeyField = "private-key" + // SecretPublicKeyField is the name of the field inside the Secret resource that contains the public key. + SecretPublicKeyField = "public-key" +) + +// EnsureKeysSecret ensure the presence of the private and public keys for the Wireguard interface and save them inside a Secret resource and Options. +func EnsureKeysSecret(ctx context.Context, cl client.Client, opts *Options) error { + var pri, pub wgtypes.Key + var err error + pri, err = CheckKeysSecret(ctx, cl, opts) + + switch { + case kerrors.IsNotFound(err) || len(pri) == 0: + pri, err = wgtypes.GeneratePrivateKey() + if err != nil { + return err + } + pub = pri.PublicKey() + if err := CreateKeysSecret(ctx, cl, opts, pri, pub); err != nil { + return err + } + case err != nil: + return err + } + + opts.PrivateKey = pri + + return nil +} diff --git a/pkg/gateway/tunnel/wireguard/labels.go b/pkg/gateway/tunnel/wireguard/labels.go new file mode 100644 index 0000000000..f7377f2456 --- /dev/null +++ b/pkg/gateway/tunnel/wireguard/labels.go @@ -0,0 +1,23 @@ +// Copyright 2019-2023 The Liqo 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 wireguard + +// Labels is the type used to identify the wireguard labels. +type Labels string + +const ( + // LabelsMode is the label used to identify the wireguard mode (server/client). + LabelsMode Labels = "networking.liqo.io/wireguard-mode" +) diff --git a/pkg/gateway/tunnel/wireguard/netlink.go b/pkg/gateway/tunnel/wireguard/netlink.go new file mode 100644 index 0000000000..4a3771ab3f --- /dev/null +++ b/pkg/gateway/tunnel/wireguard/netlink.go @@ -0,0 +1,54 @@ +// Copyright 2019-2023 The Liqo 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 wireguard + +import ( + "github.com/vishvananda/netlink" + + "github.com/liqotech/liqo/pkg/gateway/tunnel/common" +) + +// InitWireguardLink inits the Wireguard interface. +func InitWireguardLink(options *Options) error { + if err := createLink(options); err != nil { + return err + } + + link, err := common.GetLink(options.InterfaceName) + if err != nil { + return err + } + + if err := common.AddAddress(link, options.InterfaceIP); err != nil { + return err + } + + return netlink.LinkSetUp(link) +} + +// CreateLink creates a new Wireguard interface. +func createLink(options *Options) error { + link := netlink.Wireguard{ + LinkAttrs: netlink.LinkAttrs{ + MTU: options.MTU, + Name: options.InterfaceName, + }, + } + err := netlink.LinkAdd(&link) + if err != nil { + return err + } + return nil +} diff --git a/pkg/gateway/tunnel/wireguard/options.go b/pkg/gateway/tunnel/wireguard/options.go new file mode 100644 index 0000000000..f70cee7cae --- /dev/null +++ b/pkg/gateway/tunnel/wireguard/options.go @@ -0,0 +1,58 @@ +// Copyright 2019-2023 The Liqo 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 wireguard + +import ( + "net" + "sync" + "time" + + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + + "github.com/liqotech/liqo/pkg/gateway/tunnel/common" +) + +// Options contains the options for the wireguard interface. +type Options struct { + Name string + Namespace string + RemoteClusterID string + WireguardGatewayUID string + + Mode common.Mode + MTU int + PrivateKey wgtypes.Key + InterfaceName string + InterfaceIP string + ListenPort int + EndpointAddress string + EndpointPort int + + EndpointIP net.IP + EndpointIPMutex *sync.Mutex + + DNSCheckInterval time.Duration + + LeaderElection bool + MetricsAddress string + ProbeAddr string +} + +// NewOptions returns a new Options struct. +func NewOptions() *Options { + return &Options{ + EndpointIPMutex: &sync.Mutex{}, + } +} diff --git a/pkg/gateway/tunnel/wireguard/publickeys-controller.go b/pkg/gateway/tunnel/wireguard/publickeys-controller.go new file mode 100644 index 0000000000..b950049207 --- /dev/null +++ b/pkg/gateway/tunnel/wireguard/publickeys-controller.go @@ -0,0 +1,117 @@ +// Copyright 2019-2023 The Liqo 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 wireguard + +import ( + "context" + "fmt" + + "golang.zx2c4.com/wireguard/wgctrl" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" + gwlabels "github.com/liqotech/liqo/pkg/gateway/common/labels" + "github.com/liqotech/liqo/pkg/gateway/tunnel/common" +) + +// cluster-role +// +kubebuilder:rbac:groups=networking.liqo.io,resources=publickeys,verbs=get;list;create;delete;update + +// PublicKeysReconciler updates the PublicKey resource used to establish the Wireguard connection. +type PublicKeysReconciler struct { + Wgcl *wgctrl.Client + Client client.Client + Scheme *runtime.Scheme + EventsRecorder record.EventRecorder + Options *Options +} + +// NewPublicKeysReconciler returns a new PublicKeysReconciler. +func NewPublicKeysReconciler(cl client.Client, s *runtime.Scheme, er record.EventRecorder, options *Options) (*PublicKeysReconciler, error) { + wgcl, err := wgctrl.New() + if err != nil { + return nil, fmt.Errorf("unable to create wireguard client: %w", err) + } + return &PublicKeysReconciler{ + Wgcl: wgcl, + Client: cl, + Scheme: s, + EventsRecorder: er, + Options: options, + }, nil +} + +// Reconcile manage PublicKey resources. +func (r *PublicKeysReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + publicKey := &networkingv1alpha1.PublicKey{} + if err := r.Client.Get(ctx, req.NamespacedName, publicKey); err != nil { + if apierrors.IsNotFound(err) { + klog.Infof("There is no publicKey %s", req.String()) + return ctrl.Result{}, nil + } + return ctrl.Result{}, fmt.Errorf("unable to get the publicKey %q: %w", req.NamespacedName, err) + } + + switch r.Options.Mode { + case common.ModeServer: + if err := configureServerDevice(r.Wgcl, r.Options, wgtypes.Key(publicKey.Spec.PublicKey)); err != nil { + return ctrl.Result{}, err + } + case common.ModeClient: + if err := configureClientDevice(r.Wgcl, r.Options, wgtypes.Key(publicKey.Spec.PublicKey)); err != nil { + return ctrl.Result{}, err + } + } + + return ctrl.Result{}, r.EnsureConnection(ctx) +} + +// SetupWithManager register the ConfigurationReconciler to the manager. +func (r *PublicKeysReconciler) SetupWithManager(mgr ctrl.Manager, src <-chan event.GenericEvent) error { + watcherDNS := NewWatcherDNS(src, r.Client, r.Options) + + return ctrl.NewControllerManagedBy(mgr). + For(&networkingv1alpha1.PublicKey{}, r.Predicates()). + WatchesRawSource(&watcherDNS.Source, watcherDNS.EventHandler). + Complete(r) +} + +// Predicates returns the predicates required for the PublicKey controller. +func (r *PublicKeysReconciler) Predicates() builder.Predicates { + return builder.WithPredicates( + predicate.NewPredicateFuncs(func(object client.Object) bool { + mode, ok := object.GetLabels()[string(LabelsMode)] + if !ok { + return false + } + if mode != string(r.Options.Mode) { + return false + } + id, ok := object.GetLabels()[string(gwlabels.LabelsRemoteClusterID)] + if !ok { + return false + } + return id == r.Options.RemoteClusterID + })) +} diff --git a/pkg/liqonet/tunnel/driver.go b/pkg/liqonet/tunnel/driver.go index 3bce59c83d..d6389c8f23 100644 --- a/pkg/liqonet/tunnel/driver.go +++ b/pkg/liqonet/tunnel/driver.go @@ -21,7 +21,7 @@ import ( "k8s.io/klog/v2" netv1alpha1 "github.com/liqotech/liqo/apis/net/v1alpha1" - "github.com/liqotech/liqo/pkg/liqonet/conncheck" + "github.com/liqotech/liqo/pkg/gateway/connection/conncheck" ) // DriverCreateFunc function prototype to create a new driver. diff --git a/pkg/liqonet/tunnel/wireguard/driver.go b/pkg/liqonet/tunnel/wireguard/driver.go index 16e872e9de..0f374d415d 100644 --- a/pkg/liqonet/tunnel/wireguard/driver.go +++ b/pkg/liqonet/tunnel/wireguard/driver.go @@ -42,7 +42,7 @@ import ( discv1alpha1 "github.com/liqotech/liqo/apis/discovery/v1alpha1" netv1alpha1 "github.com/liqotech/liqo/apis/net/v1alpha1" liqoconst "github.com/liqotech/liqo/pkg/consts" - "github.com/liqotech/liqo/pkg/liqonet/conncheck" + "github.com/liqotech/liqo/pkg/gateway/connection/conncheck" "github.com/liqotech/liqo/pkg/liqonet/tunnel" "github.com/liqotech/liqo/pkg/liqonet/tunnel/metrics" "github.com/liqotech/liqo/pkg/liqonet/tunnel/resolver" diff --git a/pkg/utils/getters/k8sGetters.go b/pkg/utils/getters/k8sGetters.go index e444395285..d628e0ffdd 100644 --- a/pkg/utils/getters/k8sGetters.go +++ b/pkg/utils/getters/k8sGetters.go @@ -29,6 +29,7 @@ import ( discoveryv1alpha1 "github.com/liqotech/liqo/apis/discovery/v1alpha1" ipamv1alpha1 "github.com/liqotech/liqo/apis/ipam/v1alpha1" netv1alpha1 "github.com/liqotech/liqo/apis/net/v1alpha1" + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" offloadingv1alpha1 "github.com/liqotech/liqo/apis/offloading/v1alpha1" sharingv1alpha1 "github.com/liqotech/liqo/apis/sharing/v1alpha1" virtualkubeletv1alpha1 "github.com/liqotech/liqo/apis/virtualkubelet/v1alpha1" @@ -336,3 +337,13 @@ func ListNetworkByLabel(ctx context.Context, cl client.Client, ns string, lSelec } return list, err } + +// ListPublicKeysByLabel returns the PublicKey resource with the given labels. +func ListPublicKeysByLabel(ctx context.Context, cl client.Client, ns string, lSelector labels.Selector) (*networkingv1alpha1.PublicKeyList, error) { + list := &networkingv1alpha1.PublicKeyList{} + err := cl.List(ctx, list, &client.ListOptions{LabelSelector: lSelector}, client.InNamespace(ns)) + if err != nil { + return nil, err + } + return list, err +}