package store import ( "context" "fmt" "net" "reflect" "time" iamAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/iam/v1beta1" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/k8s/v1alpha1" secretmanagerAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/secretmanager/v1beta1" emissaryv3alpha1 "github.com/emissary-ingress/emissary/v3/pkg/api/getambassador.io/v3alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "k8s.io/apimachinery/pkg/api/errors" etypes "edge-infra.dev/pkg/edge/api/types" v1cluster "edge-infra.dev/pkg/edge/apis/cluster/v1alpha1" v1alpha1syncedobject "edge-infra.dev/pkg/edge/apis/syncedobject/apis/v1alpha1" "edge-infra.dev/pkg/edge/clientutils" "edge-infra.dev/pkg/edge/k8objectsutils" "edge-infra.dev/pkg/lib/uuid" "edge-infra.dev/pkg/sds/ingress/emissary" "edge-infra.dev/pkg/sds/remoteaccess/constants" v1vpnconfig "edge-infra.dev/pkg/sds/remoteaccess/k8s/apis/vpnconfigs/v1" wg "edge-infra.dev/pkg/sds/remoteaccess/wireguard" secrets "edge-infra.dev/pkg/sds/remoteaccess/wireguard/secret" ) type Store struct { IsEnabled bool ClusterEdgeID string ClusterName string *wg.Instance } // Retrieves the store wireguard instance and creates a new one if it does not exist func Get(ctx context.Context, c client.Client, vpnConfig *v1vpnconfig.VPNConfig, cluster *v1cluster.Cluster) (*Store, error) { wg, err := wg.GetInstance(ctx, c, constants.StoreName, cluster.ObjectMeta.Name, vpnConfig) return &Store{ Instance: wg, IsEnabled: vpnConfig.IsEnabled(), ClusterName: cluster.Spec.Name, ClusterEdgeID: cluster.ObjectMeta.Name, }, err } func (s *Store) SetEnabled(enabled bool) { s.IsEnabled = enabled } // Updates the store external secret and deployment func (s *Store) UpdateWireguardSecret(ctx context.Context, c client.Client, subnetCIDR *net.IPNet, clientIP, relayExternalIP net.IP, relayPublicKey string, sm etypes.SecretManagerService, cluster *v1cluster.Cluster) error { secretData := s.GenerateConfigurationSecretData(subnetCIDR, clientIP, relayExternalIP, relayPublicKey) if err := secrets.SaveStoreSecret(ctx, s.ClusterEdgeID, secretData, sm); err != nil { return err } return createOrUpdateExternalSecretSyncedObject(ctx, c, cluster) } func (s *Store) GenerateConfigurationSecretData(subnetCIDR *net.IPNet, clientIP, relayExternalIP net.IP, relayPublicKey string) []byte { storeConfig := s.wg0ConfigString(subnetCIDR, clientIP, relayExternalIP, relayPublicKey) return []byte(storeConfig) } // The relay wg0 configuration file contents func (s *Store) wg0ConfigString(subnetCIDR *net.IPNet, clientIP, relayExternalIP net.IP, relayPublicKey string) string { interfaceConfig := s.interfaceConfigString(subnetCIDR) storeConfig := s.peerConfigString(clientIP, relayExternalIP, relayPublicKey) return interfaceConfig + storeConfig } func (s *Store) interfaceConfigString(subnetCIDR *net.IPNet) string { prefLen, _ := subnetCIDR.Mask.Size() return fmt.Sprintf( "[Interface]\nPrivateKey = %s\nAddress = %s/%d\nMTU = %s\n", s.GetPrivateKey(), s.GetIPAddress(), prefLen, constants.MTU, ) } func (s *Store) peerConfigString(clientIP, relayExternalIP net.IP, relayPublicKey string) string { return fmt.Sprintf( "\n[Peer]\nPersistentKeepalive = 25\nEndpoint = %s:51820\nPublicKey = %s\nAllowedIPs = %s/32\n", relayExternalIP, relayPublicKey, clientIP, ) } func (s *Store) RemoveWireguardSecret(ctx context.Context, c client.Client, sm etypes.SecretManagerService) error { if err := secrets.RemoveStoreSecret(ctx, s.ClusterEdgeID, sm); err != nil { return err } return removeExternalSecretSyncedObject(ctx, c, s.ClusterEdgeID) } func (s *Store) UpdateEmissaryMapping(ctx context.Context, c client.Client, clusterName string) error { hostname, err := getEmissaryHostname(ctx, c) if err != nil { return err } mapping := s.emissaryMapping(hostname, clusterName) return createOrPatchEmissaryMapping(ctx, c, mapping) } func createOrPatchEmissaryMapping(ctx context.Context, c client.Client, mapping *emissaryv3alpha1.Mapping) error { currentMapping := &emissaryv3alpha1.Mapping{} err := c.Get(ctx, client.ObjectKeyFromObject(mapping), currentMapping) if errors.IsNotFound(err) { return c.Create(ctx, mapping) } else if err != nil { return err } // Emissary API needs the resource version to be set explicitly to patch mapping.ObjectMeta.ResourceVersion = currentMapping.ObjectMeta.ResourceVersion return c.Patch(ctx, mapping, client.MergeFrom(currentMapping.DeepCopy())) } func (s *Store) RemoveEmissaryMapping(ctx context.Context, c client.Client) error { mapping := &emissaryv3alpha1.Mapping{} mappingName := fmt.Sprintf("%s-%s", constants.StoreName, s.ClusterEdgeID) key := client.ObjectKey{Namespace: emissary.IngressNamespace, Name: mappingName} if err := c.Get(ctx, key, mapping); errors.IsNotFound(err) { return nil } else if err != nil { return err } return c.Delete(ctx, mapping) } func getEmissaryHostname(ctx context.Context, c client.Client) (string, error) { key := client.ObjectKey{Namespace: emissary.IngressNamespace, Name: emissary.RemoteAccessHostName} host := &emissaryv3alpha1.Host{} if err := c.Get(ctx, key, host); err != nil { return "", err } return host.Spec.Hostname, nil } func (s *Store) emissaryMapping(hostname, clusterName string) *emissaryv3alpha1.Mapping { prefixRegex := true requestHeadersToRemove := []string{"authorization"} return &emissaryv3alpha1.Mapping{ ObjectMeta: metav1.ObjectMeta{ Namespace: emissary.IngressNamespace, Name: fmt.Sprintf("%s-%s", constants.StoreName, s.ClusterEdgeID), }, TypeMeta: metav1.TypeMeta{ Kind: reflect.TypeOf(emissaryv3alpha1.Mapping{}).Name(), APIVersion: emissaryv3alpha1.SchemeBuilder.GroupVersion.Group + "/" + emissaryv3alpha1.SchemeBuilder.GroupVersion.Version, }, Spec: emissaryv3alpha1.MappingSpec{ Prefix: fmt.Sprintf("/remoteaccess/%s/.*", clusterName), Service: s.GetIPAddress().String(), Hostname: hostname, PrefixRegex: &prefixRegex, RegexRewrite: &emissaryv3alpha1.RegexMap{ Pattern: fmt.Sprintf("/remoteaccess/%s/(.*)", clusterName), Substitution: "/\\1", }, AllowUpgrade: []string{"websocket"}, Timeout: &emissaryv3alpha1.MillisecondDuration{Duration: time.Millisecond * 60000}, ConnectTimeout: &emissaryv3alpha1.MillisecondDuration{Duration: time.Millisecond * 60000}, RemoveRequestHeaders: &requestHeadersToRemove, }, } } func createOrUpdateExternalSecretSyncedObject(ctx context.Context, c client.Client, cluster *v1cluster.Cluster) error { smSecretName := k8objectsutils.NameWithPrefix(constants.StoreName, cluster.ObjectMeta.Name) member := fmt.Sprintf("serviceAccount:ext-sec-%s@%s.iam.gserviceaccount.com", uuid.FromUUID(cluster.ObjectMeta.Name).Hash(), cluster.Spec.ProjectID) pMember := &iamAPI.IAMPolicyMember{ ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("essa-%s", smSecretName), Namespace: cluster.ObjectMeta.Name, }, TypeMeta: metav1.TypeMeta{ APIVersion: iamAPI.IAMPolicyMemberGVK.GroupVersion().String(), Kind: iamAPI.IAMPolicyMemberGVK.Kind, }, Spec: iamAPI.IAMPolicyMemberSpec{ Member: &member, ResourceRef: v1alpha1.IAMResourceRef{ APIVersion: secretmanagerAPI.SecretManagerSecretGVK.GroupVersion().String(), Kind: secretmanagerAPI.SecretManagerSecretGVK.Kind, External: fmt.Sprintf("projects/%s/secrets/%s", cluster.Spec.ProjectID, smSecretName), }, Role: "roles/secretmanager.secretAccessor", }, } err := clientutils.CreateOrUpdatePolicyMember(ctx, c, pMember) if err != nil { return err } externalSecret := k8objectsutils.BuildExternalSecret( cluster.Spec.ProjectID, smSecretName, constants.VPNNamespace, constants.StoreName, constants.WireguardSecretField, ) prefix := fmt.Sprintf("%s-externalsecret", constants.StoreName) syncedObject, err := k8objectsutils.BuildClusterSyncedObject(cluster, externalSecret, prefix) if err != nil { return err } return clientutils.CreateOrUpdateSyncedObject(ctx, c, syncedObject) } func removeExternalSecretSyncedObject(ctx context.Context, c client.Client, clusterEdgeID string) error { syncedObjectName := fmt.Sprintf("%s-externalsecret-%s", constants.StoreName, clusterEdgeID) key := client.ObjectKey{Namespace: clusterEdgeID, Name: syncedObjectName} return removeSyncedObject(ctx, c, key) } func removeSyncedObject(ctx context.Context, c client.Client, key client.ObjectKey) error { syncedObject := &v1alpha1syncedobject.SyncedObject{} if err := c.Get(ctx, key, syncedObject); errors.IsNotFound(err) { return nil } else if err != nil { return err } return c.Delete(ctx, syncedObject) }