package client import ( "context" "fmt" "net" "strings" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" "edge-infra.dev/pkg/k8s/runtime/objectrestarter" "edge-infra.dev/pkg/sds/ingress/emissary" "edge-infra.dev/pkg/sds/remoteaccess/constants" wg "edge-infra.dev/pkg/sds/remoteaccess/wireguard" secrets "edge-infra.dev/pkg/sds/remoteaccess/wireguard/secret" "edge-infra.dev/pkg/sds/remoteaccess/wireguard/store" ) type Client struct { *wg.Instance } // Retrieves the client wireguard instance and creates a new one if it does not exist func Get(ctx context.Context, c client.Client) (*Client, error) { wg, err := wg.GetInstance(ctx, c, constants.ClientName, "cluster-infra", nil) return &Client{Instance: wg}, err } var emissaryIngressLabelSelector = labels.Set{constants.K8sNameLabel: emissary.IngressName} func (cl *Client) UpdateWireguardSecret(ctx context.Context, c client.Client, subnetCIDR *net.IPNet, relayExternalIPAddress net.IP, relayPublicKey string, stores map[string]*store.Store) error { secret := cl.GenerateConfigurationSecret(subnetCIDR, relayExternalIPAddress, relayPublicKey, stores) if err := secrets.CreateOrPatchSecret(ctx, c, secret); err != nil { return err } return cl.updateEmissaryDeployment(ctx, c) } func (cl *Client) GenerateConfigurationSecret(subnetCIDR *net.IPNet, relayExternalIPAddress net.IP, relayPublicKey string, storeConfigs map[string]*store.Store) *corev1.Secret { wg0ConfigString := cl.wg0ConfigString(subnetCIDR, relayExternalIPAddress, relayPublicKey, storeConfigs) return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: emissary.IngressNamespace, Name: constants.ClientName, }, StringData: map[string]string{constants.WireguardSecretField: wg0ConfigString}, } } func (cl *Client) wg0ConfigString(subnetCIDR *net.IPNet, relayExternalIP net.IP, relayPublicKey string, storeConfigs map[string]*store.Store) string { allowedIPs := getAllowedIPs(storeConfigs) interfaceConfig := cl.interfaceConfigString(subnetCIDR) clientConfig := cl.peerConfigString(relayExternalIP, relayPublicKey, allowedIPs) return interfaceConfig + clientConfig } func getAllowedIPs(storeConfigs map[string]*store.Store) []string { var allowedIPs []string for _, storeConfig := range storeConfigs { if storeConfig.IsEnabled { allowedIPs = append(allowedIPs, fmt.Sprintf("%s/32", storeConfig.GetIPAddress())) } } return allowedIPs } func (cl *Client) interfaceConfigString(subnetCIDR *net.IPNet) string { prefLen, _ := subnetCIDR.Mask.Size() return fmt.Sprintf( "[Interface]\nPrivateKey = %s\nAddress = %s/%d\nMTU = %s\n", cl.GetPrivateKey(), cl.GetIPAddress(), prefLen, constants.MTU, ) } func (cl *Client) peerConfigString(relayExternalIP net.IP, relayPublicKey string, allowedIPs []string) string { peerConfigString := fmt.Sprintf( "\n[Peer]\nPersistentKeepalive = 25\nEndpoint = %s:51820\nPublicKey = %s\n", relayExternalIP, relayPublicKey, ) if len(allowedIPs) > 0 { peerConfigString = fmt.Sprintf("%sAllowedIPs = %s\n", peerConfigString, strings.Join(allowedIPs, ", ")) } return peerConfigString } // A mutating webhook will add a Wireguard container to the Emissary pod on creation. If the emissary pod was created before the webhook // configuration this will not happen. This method deletes the pod if only one container exists so the webhook configuration can apply. func (cl *Client) updateEmissaryDeployment(ctx context.Context, c client.Client) error { if injected, err := emissaryDeploymentIsInjected(ctx, c); err != nil { return err } else if injected { return nil // do nothing if already injected } // restart deployment so Wireguard can be injected deployment := &appsv1.Deployment{} if err := c.Get(ctx, client.ObjectKey{Namespace: emissary.IngressNamespace, Name: emissary.IngressName}, deployment); err != nil { return err } return objectrestarter.Restart(ctx, c, deployment) } func emissaryDeploymentIsInjected(ctx context.Context, c client.Client) (bool, error) { podList := &corev1.PodList{} if err := c.List(ctx, podList, &client.ListOptions{ LabelSelector: labels.SelectorFromSet(emissaryIngressLabelSelector), Namespace: emissary.IngressNamespace, }); err != nil { return false, err } // delete any emissary-ingress pods without a Wireguard container so it can be added by the webhook on re-creation for _, pod := range podList.Items { if !hasWireguardContainer(pod) { return false, nil } } return true, nil } func hasWireguardContainer(pod corev1.Pod) bool { for _, container := range pod.Spec.Containers { if container.Name == constants.WireguardContainerName { return true } } return false }