Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Wireguard Tunnel Container
Browse files Browse the repository at this point in the history
cheina97 committed Oct 18, 2023
1 parent dee6d96 commit 3e21109
Showing 24 changed files with 1,183 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -34,3 +34,8 @@ updates:
directory: "/build/proxy"
schedule:
interval: "daily"

- package-ecosystem: "docker"
directory: "/build/gateway/tunnel/wireguard"
schedule:
interval: "daily"
2 changes: 1 addition & 1 deletion .github/workflows/integration.yml
Original file line number Diff line number Diff line change
@@ -90,8 +90,8 @@ jobs:
- metric-agent
- telemetry
- proxy
- gateway/wireguard
steps:

- name: Set up QEMU
uses: docker/[email protected]
with:
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -75,6 +75,7 @@ rbacs: controller-gen
$(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="./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_COMMAND) deployments/liqo/files/liqo-gateway-ClusterRole.yaml

# Install gci if not available
gci:
20 changes: 20 additions & 0 deletions build/gateway/wireguard/Dockerfile
Original file line number Diff line number Diff line change
@@ -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/wireguard


FROM alpine:3.18

RUN apk update && \
apk add iptables bash wireguard-tools tcpdump conntrack-tools curl iputils && \
rm -rf /var/cache/apk/*

COPY --from=goBuilder /tmp/builder/wireguard /usr/bin/liqo-wireguard

ENTRYPOINT [ "/usr/bin/liqo-wireguard" ]
164 changes: 164 additions & 0 deletions cmd/gateway/wireguard/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// 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"
"fmt"
"net"
"os"

"github.com/spf13/cobra"
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/common"
"github.com/liqotech/liqo/pkg/gateway/tunnel/wireguard"
flagsutils "github.com/liqotech/liqo/pkg/utils/flags"
"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,
}
options = wireguard.NewOptions()
)

func main() {
var cmd = cobra.Command{
Use: "liqo-wireguard",
RunE: run,
}

legacyflags := flag.NewFlagSet("legacy", flag.ExitOnError)
restcfg.InitFlags(legacyflags)
klog.InitFlags(legacyflags)
flagsutils.FromFlagToPflag(legacyflags, cmd.Flags())

wireguard.InitFlags(cmd.Flags(), options)
if err := wireguard.MarkFlagsRequired(&cmd, options); err != nil {
klog.Error(err)
os.Exit(1)
}

if err := cmd.Execute(); err != nil {
klog.Error(err)
os.Exit(1)
}
}

func run(cmd *cobra.Command, _ []string) error {
var err error
scheme := runtime.NewScheme()

// Adds the APIs to the scheme.
for _, addToScheme := range addToSchemeFunctions {
if err = addToScheme(scheme); err != nil {
return fmt.Errorf("unable to add scheme: %w", err)
}
}

// Set controller-runtime logger.
log.SetLogger(klog.NewKlogr())

// Get the rest config.
cfg := config.GetConfigOrDie()

// Create the client. This client should be used only outside the reconciler.
// This client don't need a cache.
cl, err := client.New(cfg, client.Options{
Scheme: scheme,
})
if err != nil {
return fmt.Errorf("unable to create client: %w", err)
}

// 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: fmt.Sprintf(
"%s.%s.%s.wgtunnel.liqo.io",
wireguard.GenerateResourceName(options.Name), options.Namespace, options.Mode,
),
LeaderElectionNamespace: options.Namespace,
LeaderElectionReleaseOnCancel: true,
LeaderElectionResourceLock: resourcelock.LeasesResourceLock,
LeaseDuration: &options.LeaderElectionLeaseDuration,
RenewDeadline: &options.LeaderElectionRenewDeadline,
RetryPeriod: &options.LeaderElectionRetryPeriod,
})
if err != nil {
return fmt.Errorf("unable to create manager: %w", err)
}

// Setup the controller.
pkr, err := wireguard.NewPublicKeysReconciler(
mgr.GetClient(),
mgr.GetScheme(),
mgr.GetEventRecorderFor("public-keys-controller"),
options,
)
if err != nil {
return fmt.Errorf("unable to create public keys reconciler: %w", err)
}

dnsChan := make(chan event.GenericEvent)
if options.Mode == common.ModeClient {
if wireguard.IsDNSRoutineRequired(options) {
go wireguard.StartDNSRoutine(cmd.Context(), 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.String())
}
}

// Setup the controller.
if err = pkr.SetupWithManager(mgr, dnsChan); err != nil {
return fmt.Errorf("unable to setup public keys reconciler: %w", err)
}

// Ensure presence of Secret with private and public keys.
if err = wireguard.EnsureKeysSecret(cmd.Context(), cl, options); err != nil {
return fmt.Errorf("unable to manage wireguard keys secret: %w", err)
}

// Create the wg-liqo interface and init the wireguard configuration depending on the mode (client/server).
if err := wireguard.InitWireguardLink(options); err != nil {
return fmt.Errorf("unable to init wireguard link: %w", err)
}

// Start the manager.
return mgr.Start(cmd.Context())
}
21 changes: 21 additions & 0 deletions deployments/liqo/files/liqo-gateway-ClusterRole.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- delete
- get
- list
- update
- apiGroups:
- networking.liqo.io
resources:
- publickeys
verbs:
- create
- delete
- get
- list
- update
14 changes: 14 additions & 0 deletions deployments/liqo/templates/liqo-gateway-rbac.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
{{- $gatewayConfig := (merge (dict "name" "gateway" "module" "networking") .) -}}

{{- if .Values.networking.internal }}

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "liqo.prefixedName" $gatewayConfig }}
labels:
{{- include "liqo.labels" $gatewayConfig | nindent 4 }}
{{ .Files.Get (include "liqo.cluster-role-filename" (dict "prefix" ( include "liqo.prefixedName" $gatewayConfig))) }}

{{- end }}
2 changes: 2 additions & 0 deletions pkg/consts/externalnetwork.go
Original file line number Diff line number Diff line change
@@ -30,6 +30,8 @@ const (
// GatewayTypeClient indicates a Gateway of type client.
GatewayTypeClient = "client"

// PrivateKeyField is the data field of the secrets containing private keys.
PrivateKeyField = "privateKey"
// PublicKeyField is the data field of the secrets containing public keys.
PublicKeyField = "publicKey"
)
16 changes: 16 additions & 0 deletions pkg/gateway/tunnel/common/doc.go
Original file line number Diff line number Diff line change
@@ -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
32 changes: 32 additions & 0 deletions pkg/gateway/tunnel/common/netlink.go
Original file line number Diff line number Diff line change
@@ -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)
}
51 changes: 51 additions & 0 deletions pkg/gateway/tunnel/common/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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
}

// Type returns the type of the mode.
func (m *Mode) Type() string {
return "string"
}
50 changes: 50 additions & 0 deletions pkg/gateway/tunnel/wireguard/device.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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"

"github.com/liqotech/liqo/pkg/gateway/tunnel/common"
)

func configureDevice(wgcl *wgctrl.Client, options *Options, peerPubKey wgtypes.Key) error {
confdev := 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)}},
},
},
ReplacePeers: true,
}

switch options.Mode {
case common.ModeServer:
confdev.ListenPort = &options.ListenPort
case common.ModeClient:
confdev.Peers[0].Endpoint = &net.UDPAddr{
IP: options.EndpointIP,
Port: options.EndpointPort,
}
}

return wgcl.ConfigureDevice(options.InterfaceName, confdev)
}
128 changes: 128 additions & 0 deletions pkg/gateway/tunnel/wireguard/dns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// 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"

"github.com/liqotech/liqo/pkg/consts"
"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()
if len(ips) == 0 {
return false, nil
}
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
}

// NewDNSSource creates a new Source for the DNS watcher.
func NewDNSSource(src <-chan event.GenericEvent) *source.Channel {
return &source.Channel{
Source: src,
}
}

// NewDNSEventHandler creates a new EventHandler.
func NewDNSEventHandler(cl client.Client, opts *Options) handler.EventHandler {
return handler.EnqueueRequestsFromMapFunc(
func(ctx context.Context, _ client.Object) []reconcile.Request {
labelSet := labels.Set{
string(LabelsMode): string(opts.Mode),
string(consts.RemoteClusterID): 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}},
}
})
}
16 changes: 16 additions & 0 deletions pkg/gateway/tunnel/wireguard/doc.go
Original file line number Diff line number Diff line change
@@ -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
133 changes: 133 additions & 0 deletions pkg/gateway/tunnel/wireguard/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// 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 (
"time"

"github.com/spf13/cobra"
"github.com/spf13/pflag"

"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"
// FlagNameGatewayUID is the UID of the wireguard gateway.
FlagNameGatewayUID FlagName = "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"

// FlagNameLeaderElection is the flag to enable leader election.
FlagNameLeaderElection FlagName = "leader-election"
// FlagNameLeaderElectionLeaseDuration is the lease duration for the leader election.
FlagNameLeaderElectionLeaseDuration FlagName = "leader-election-lease-duration"
// FlagNameLeaderElectionRenewDeadline is the renew deadline for the leader election.
FlagNameLeaderElectionRenewDeadline FlagName = "leader-election-renew-deadline"
// FlagNameLeaderElectionRetryPeriod is the retry period for the leader election.
FlagNameLeaderElectionRetryPeriod FlagName = "leader-election-retry-period"

// FlagNameMetricsAddress is the address for the metrics endpoint.
FlagNameMetricsAddress FlagName = "metrics-address"
// FlagNameProbeAddr is the address for the health probe endpoint.
FlagNameProbeAddr FlagName = "health-probe-bind-address"
)

// RequiredFlags contains the list of the mandatory flags.
var RequiredFlags = []FlagName{
FlagNameName,
FlagNameNamespace,
FlagNameRemoteClusterID,
FlagNameGatewayUID,
FlagNameMode,
}

// ClientRequiredFlags contains the list of the mandatory flags for the client mode.
var ClientRequiredFlags = []FlagName{
FlagNameEndpointAddress,
}

// InitFlags initializes the flags for the wireguard tunnel.
func InitFlags(flagset *pflag.FlagSet, opts *Options) {
flagset.StringVar(&opts.Name, FlagNameName.String(), "", "Parent gateway name")
flagset.StringVar(&opts.Namespace, FlagNameNamespace.String(), "", "Parent gateway namespace")
flagset.StringVar(&opts.RemoteClusterID, FlagNameRemoteClusterID.String(), "", "ClusterID of the remote cluster")
flagset.StringVar(&opts.GatewayUID, FlagNameGatewayUID.String(), "", "Parent gateway resource UID")

flagset.Var(&opts.Mode, FlagNameMode.String(), "Parent gateway mode")
flagset.IntVar(&opts.MTU, FlagNameMTU.String(), 1420, "MTU for the interface")
flagset.StringVar(&opts.InterfaceName, FlagNameInterfaceName.String(), "liqo-tunnel", "Name for the tunnel interface")
flagset.IntVar(&opts.ListenPort, FlagNameListenPort.String(), 51820, "Listen port (server only)")
flagset.StringVar(&opts.EndpointAddress, FlagNameEndpointAddress.String(), "", "Endpoint address (client only)")
flagset.IntVar(&opts.EndpointPort, FlagNameEndpointPort.String(), 51820, "Endpoint port (client only)")

flagset.DurationVar(&opts.DNSCheckInterval, FlagNameDNSCheckInterval.String(), 5*time.Minute, "Interval between two DNS checks")

flagset.BoolVar(&opts.LeaderElection, FlagNameLeaderElection.String(), false, "Enable leader election")
flagset.DurationVar(&opts.LeaderElectionLeaseDuration, FlagNameLeaderElectionLeaseDuration.String(), 15*time.Second,
"LeaseDuration for the leader election")
flagset.DurationVar(&opts.LeaderElectionRenewDeadline, FlagNameLeaderElectionRenewDeadline.String(), 10*time.Second,
"RenewDeadline for the leader election")
flagset.DurationVar(&opts.LeaderElectionRetryPeriod, FlagNameLeaderElectionRetryPeriod.String(), 2*time.Second,
"RetryPeriod for the leader election")

flagset.StringVar(&opts.MetricsAddress, FlagNameMetricsAddress.String(), ":8080", "Address for the metrics endpoint")
flagset.StringVar(&opts.ProbeAddr, FlagNameProbeAddr.String(), ":8081", "Address for the health probe endpoint")
}

// MarkFlagsRequired marks the flags as required.
func MarkFlagsRequired(cmd *cobra.Command, opts *Options) error {
for _, flag := range RequiredFlags {
if err := cmd.MarkFlagRequired(flag.String()); err != nil {
return err
}
}
if opts.Mode == common.ModeClient {
for _, flag := range ClientRequiredFlags {
if err := cmd.MarkFlagRequired(flag.String()); err != nil {
return err
}
}
}
return nil
}
122 changes: 122 additions & 0 deletions pkg/gateway/tunnel/wireguard/k8s.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// 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"
"github.com/liqotech/liqo/pkg/consts"
"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.GatewayUID),
}
switch opts.Mode {
case common.ModeServer:
return controllerutil.SetOwnerReference(&networkingv1alpha1.GatewayServer{ObjectMeta: meta}, obj, scheme)
case common.ModeClient:
return controllerutil.SetOwnerReference(&networkingv1alpha1.GatewayClient{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: GenerateResourceName(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[consts.PrivateKeyField]; !ok || len(k) != wgtypes.KeyLen {
return wgtypes.Key{}, nil
}
if k, ok := secret.Data[consts.PublicKeyField]; !ok || len(k) != wgtypes.KeyLen {
return wgtypes.Key{}, nil
}
return wgtypes.Key(secret.Data[consts.PrivateKeyField]), 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: GenerateResourceName(opts.Name),
Namespace: opts.Namespace,
},
}

if _, err := controllerutil.CreateOrUpdate(ctx, cl, secret, func() error {
secret.SetLabels(map[string]string{
string(consts.RemoteClusterID): opts.RemoteClusterID,
string(consts.GatewayResourceLabel): string(consts.GatewayResourceLabelValue),
})
if err := SetOwnerReferenceWithMode(opts, secret, cl.Scheme()); err != nil {
return err
}
secret.Data = map[string][]byte{
consts.PrivateKeyField: pri[:],
consts.PublicKeyField: pub[:],
}
return nil
}); err != nil {
return err
}

return nil
}

// EnsureConnection creates or updates the connection resource.
func EnsureConnection(ctx context.Context, cl client.Client, scheme *runtime.Scheme, opts *Options) error {
conn := &networkingv1alpha1.Connection{ObjectMeta: metav1.ObjectMeta{
Name: GenerateResourceName(opts.Name), Namespace: opts.Namespace,
}}
_, err := controllerutil.CreateOrUpdate(ctx, cl, conn, func() error {
if err := SetOwnerReferenceWithMode(opts, conn, scheme); err != nil {
return err
}
conn.Spec.GatewayRef.APIVersion = networkingv1alpha1.GroupVersion.String()
conn.Spec.GatewayRef.Name = opts.Name
conn.Spec.GatewayRef.Namespace = opts.Namespace
conn.Spec.GatewayRef.UID = types.UID(opts.GatewayUID)
switch opts.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
}
return nil
})

return err
}
51 changes: 51 additions & 0 deletions pkg/gateway/tunnel/wireguard/keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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

// 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
}
35 changes: 35 additions & 0 deletions pkg/gateway/tunnel/wireguard/labels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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/liqotech/liqo/pkg/gateway/tunnel/common"

// Labels is the type used to identify the wireguard labels.
type Labels string

// LabelsValue is the type used to identify the wireguard labels values.
type LabelsValue string

const (
// LabelsMode is the label used to identify the wireguard mode.
LabelsMode Labels = "networking.liqo.io/wireguard-mode"
)

const (
// LabelsModeServer is the label used to identify the wireguard mode server.
LabelsModeServer LabelsValue = LabelsValue(common.ModeServer)
// LabelsModeClient is the label used to identify the wireguard mode client.
LabelsModeClient LabelsValue = LabelsValue(common.ModeClient)
)
91 changes: 91 additions & 0 deletions pkg/gateway/tunnel/wireguard/netlink.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// 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"
"golang.zx2c4.com/wireguard/wgctrl"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"k8s.io/klog/v2"

"github.com/liqotech/liqo/pkg/gateway/tunnel/common"
)

const (
// ServerInterfaceIP is the IP address of the Wireguard interface in server mode.
ServerInterfaceIP = "169.254.0.1/30"
// ClientInterfaceIP is the IP address of the Wireguard interface in client mode.
ClientInterfaceIP = "169.254.0.2/30"
)

// 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
}

klog.Infof("Setting up Wireguard interface %q with IP %q", options.InterfaceName, GetInterfaceIP(options.Mode))
if err := common.AddAddress(link, GetInterfaceIP(options.Mode)); err != nil {
return err
}

return netlink.LinkSetUp(link)
}

// GetInterfaceIP returns the IP address of the Wireguard interface.
func GetInterfaceIP(mode common.Mode) string {
switch mode {
case common.ModeServer:
return ServerInterfaceIP
case common.ModeClient:
return ClientInterfaceIP
}
return ""
}

// 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
}

if options.Mode == common.ModeServer {
wgcl, err := wgctrl.New()
if err != nil {
return err
}
defer wgcl.Close()

if err := wgcl.ConfigureDevice(options.InterfaceName, wgtypes.Config{
ListenPort: &options.ListenPort,
}); err != nil {
return err
}
}
return nil
}
68 changes: 68 additions & 0 deletions pkg/gateway/tunnel/wireguard/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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
GatewayUID 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
LeaderElectionLeaseDuration time.Duration
LeaderElectionRenewDeadline time.Duration
LeaderElectionRetryPeriod time.Duration

MetricsAddress string
ProbeAddr string
}

// NewOptions returns a new Options struct.
func NewOptions() *Options {
return &Options{
EndpointIPMutex: &sync.Mutex{},
}
}

// GenerateResourceName generates the name used for the resources created by the gateway.
// This will help if a suffix will be added to the name of the resources in future.
func GenerateResourceName(name string) string {
return name
}
107 changes: 107 additions & 0 deletions pkg/gateway/tunnel/wireguard/publickeys-controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// 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"
"github.com/liqotech/liqo/pkg/consts"
)

// 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)
}

if err := configureDevice(r.Wgcl, r.Options, wgtypes.Key(publicKey.Spec.PublicKey)); err != nil {
return ctrl.Result{}, err
}

return ctrl.Result{}, EnsureConnection(ctx, r.Client, r.Scheme, r.Options)
}

// SetupWithManager register the ConfigurationReconciler to the manager.
func (r *PublicKeysReconciler) SetupWithManager(mgr ctrl.Manager, src <-chan event.GenericEvent) error {
return ctrl.NewControllerManagedBy(mgr).
For(&networkingv1alpha1.PublicKey{}, r.Predicates()).
WatchesRawSource(NewDNSSource(src), NewDNSEventHandler(r.Client, r.Options)).
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(consts.RemoteClusterID)]
if !ok {
return false
}
return id == r.Options.RemoteClusterID
}))
}
16 changes: 16 additions & 0 deletions pkg/utils/flags/doc.go
Original file line number Diff line number Diff line change
@@ -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 flags provides a set of utilities to manage flags.
package flags
28 changes: 28 additions & 0 deletions pkg/utils/flags/flags.go
Original file line number Diff line number Diff line change
@@ -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 flags

import (
"flag"

"github.com/spf13/pflag"
)

// FromFlagToPflag copies the flags from a flag.FlagSet to a pflag.FlagSet.
func FromFlagToPflag(flags *flag.FlagSet, pflags *pflag.FlagSet) {
flags.VisitAll(func(f *flag.Flag) {
pflags.AddGoFlag(f)
})
}
11 changes: 11 additions & 0 deletions pkg/utils/getters/k8sGetters.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 3e21109

Please sign in to comment.