...

Source file src/edge-infra.dev/pkg/sds/remoteaccess/wireguard/store/store.go

Documentation: edge-infra.dev/pkg/sds/remoteaccess/wireguard/store

     1  package store
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"reflect"
     8  	"time"
     9  
    10  	iamAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/iam/v1beta1"
    11  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/k8s/v1alpha1"
    12  	secretmanagerAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/secretmanager/v1beta1"
    13  	emissaryv3alpha1 "github.com/emissary-ingress/emissary/v3/pkg/api/getambassador.io/v3alpha1"
    14  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	"sigs.k8s.io/controller-runtime/pkg/client"
    16  
    17  	"k8s.io/apimachinery/pkg/api/errors"
    18  
    19  	etypes "edge-infra.dev/pkg/edge/api/types"
    20  	v1cluster "edge-infra.dev/pkg/edge/apis/cluster/v1alpha1"
    21  	v1alpha1syncedobject "edge-infra.dev/pkg/edge/apis/syncedobject/apis/v1alpha1"
    22  	"edge-infra.dev/pkg/edge/clientutils"
    23  	"edge-infra.dev/pkg/edge/k8objectsutils"
    24  	"edge-infra.dev/pkg/lib/uuid"
    25  	"edge-infra.dev/pkg/sds/ingress/emissary"
    26  	"edge-infra.dev/pkg/sds/remoteaccess/constants"
    27  	v1vpnconfig "edge-infra.dev/pkg/sds/remoteaccess/k8s/apis/vpnconfigs/v1"
    28  	wg "edge-infra.dev/pkg/sds/remoteaccess/wireguard"
    29  	secrets "edge-infra.dev/pkg/sds/remoteaccess/wireguard/secret"
    30  )
    31  
    32  type Store struct {
    33  	IsEnabled     bool
    34  	ClusterEdgeID string
    35  	ClusterName   string
    36  	*wg.Instance
    37  }
    38  
    39  // Retrieves the store wireguard instance and creates a new one if it does not exist
    40  func Get(ctx context.Context, c client.Client, vpnConfig *v1vpnconfig.VPNConfig, cluster *v1cluster.Cluster) (*Store, error) {
    41  	wg, err := wg.GetInstance(ctx, c, constants.StoreName, cluster.ObjectMeta.Name, vpnConfig)
    42  	return &Store{
    43  		Instance:      wg,
    44  		IsEnabled:     vpnConfig.IsEnabled(),
    45  		ClusterName:   cluster.Spec.Name,
    46  		ClusterEdgeID: cluster.ObjectMeta.Name,
    47  	}, err
    48  }
    49  
    50  func (s *Store) SetEnabled(enabled bool) {
    51  	s.IsEnabled = enabled
    52  }
    53  
    54  // Updates the store external secret and deployment
    55  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 {
    56  	secretData := s.GenerateConfigurationSecretData(subnetCIDR, clientIP, relayExternalIP, relayPublicKey)
    57  	if err := secrets.SaveStoreSecret(ctx, s.ClusterEdgeID, secretData, sm); err != nil {
    58  		return err
    59  	}
    60  	return createOrUpdateExternalSecretSyncedObject(ctx, c, cluster)
    61  }
    62  
    63  func (s *Store) GenerateConfigurationSecretData(subnetCIDR *net.IPNet, clientIP, relayExternalIP net.IP, relayPublicKey string) []byte {
    64  	storeConfig := s.wg0ConfigString(subnetCIDR, clientIP, relayExternalIP, relayPublicKey)
    65  	return []byte(storeConfig)
    66  }
    67  
    68  // The relay wg0 configuration file contents
    69  func (s *Store) wg0ConfigString(subnetCIDR *net.IPNet, clientIP, relayExternalIP net.IP, relayPublicKey string) string {
    70  	interfaceConfig := s.interfaceConfigString(subnetCIDR)
    71  	storeConfig := s.peerConfigString(clientIP, relayExternalIP, relayPublicKey)
    72  	return interfaceConfig + storeConfig
    73  }
    74  
    75  func (s *Store) interfaceConfigString(subnetCIDR *net.IPNet) string {
    76  	prefLen, _ := subnetCIDR.Mask.Size()
    77  	return fmt.Sprintf(
    78  		"[Interface]\nPrivateKey = %s\nAddress = %s/%d\nMTU = %s\n",
    79  		s.GetPrivateKey(),
    80  		s.GetIPAddress(),
    81  		prefLen,
    82  		constants.MTU,
    83  	)
    84  }
    85  
    86  func (s *Store) peerConfigString(clientIP, relayExternalIP net.IP, relayPublicKey string) string {
    87  	return fmt.Sprintf(
    88  		"\n[Peer]\nPersistentKeepalive = 25\nEndpoint = %s:51820\nPublicKey = %s\nAllowedIPs = %s/32\n",
    89  		relayExternalIP,
    90  		relayPublicKey,
    91  		clientIP,
    92  	)
    93  }
    94  
    95  func (s *Store) RemoveWireguardSecret(ctx context.Context, c client.Client, sm etypes.SecretManagerService) error {
    96  	if err := secrets.RemoveStoreSecret(ctx, s.ClusterEdgeID, sm); err != nil {
    97  		return err
    98  	}
    99  	return removeExternalSecretSyncedObject(ctx, c, s.ClusterEdgeID)
   100  }
   101  
   102  func (s *Store) UpdateEmissaryMapping(ctx context.Context, c client.Client, clusterName string) error {
   103  	hostname, err := getEmissaryHostname(ctx, c)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	mapping := s.emissaryMapping(hostname, clusterName)
   108  	return createOrPatchEmissaryMapping(ctx, c, mapping)
   109  }
   110  
   111  func createOrPatchEmissaryMapping(ctx context.Context, c client.Client, mapping *emissaryv3alpha1.Mapping) error {
   112  	currentMapping := &emissaryv3alpha1.Mapping{}
   113  	err := c.Get(ctx, client.ObjectKeyFromObject(mapping), currentMapping)
   114  	if errors.IsNotFound(err) {
   115  		return c.Create(ctx, mapping)
   116  	} else if err != nil {
   117  		return err
   118  	}
   119  
   120  	// Emissary API needs the resource version to be set explicitly to patch
   121  	mapping.ObjectMeta.ResourceVersion = currentMapping.ObjectMeta.ResourceVersion
   122  	return c.Patch(ctx, mapping, client.MergeFrom(currentMapping.DeepCopy()))
   123  }
   124  
   125  func (s *Store) RemoveEmissaryMapping(ctx context.Context, c client.Client) error {
   126  	mapping := &emissaryv3alpha1.Mapping{}
   127  	mappingName := fmt.Sprintf("%s-%s", constants.StoreName, s.ClusterEdgeID)
   128  	key := client.ObjectKey{Namespace: emissary.IngressNamespace, Name: mappingName}
   129  	if err := c.Get(ctx, key, mapping); errors.IsNotFound(err) {
   130  		return nil
   131  	} else if err != nil {
   132  		return err
   133  	}
   134  	return c.Delete(ctx, mapping)
   135  }
   136  
   137  func getEmissaryHostname(ctx context.Context, c client.Client) (string, error) {
   138  	key := client.ObjectKey{Namespace: emissary.IngressNamespace, Name: emissary.RemoteAccessHostName}
   139  	host := &emissaryv3alpha1.Host{}
   140  	if err := c.Get(ctx, key, host); err != nil {
   141  		return "", err
   142  	}
   143  	return host.Spec.Hostname, nil
   144  }
   145  
   146  func (s *Store) emissaryMapping(hostname, clusterName string) *emissaryv3alpha1.Mapping {
   147  	prefixRegex := true
   148  	requestHeadersToRemove := []string{"authorization"}
   149  	return &emissaryv3alpha1.Mapping{
   150  		ObjectMeta: metav1.ObjectMeta{
   151  			Namespace: emissary.IngressNamespace,
   152  			Name:      fmt.Sprintf("%s-%s", constants.StoreName, s.ClusterEdgeID),
   153  		},
   154  		TypeMeta: metav1.TypeMeta{
   155  			Kind:       reflect.TypeOf(emissaryv3alpha1.Mapping{}).Name(),
   156  			APIVersion: emissaryv3alpha1.SchemeBuilder.GroupVersion.Group + "/" + emissaryv3alpha1.SchemeBuilder.GroupVersion.Version,
   157  		},
   158  		Spec: emissaryv3alpha1.MappingSpec{
   159  			Prefix:      fmt.Sprintf("/remoteaccess/%s/.*", clusterName),
   160  			Service:     s.GetIPAddress().String(),
   161  			Hostname:    hostname,
   162  			PrefixRegex: &prefixRegex,
   163  			RegexRewrite: &emissaryv3alpha1.RegexMap{
   164  				Pattern:      fmt.Sprintf("/remoteaccess/%s/(.*)", clusterName),
   165  				Substitution: "/\\1",
   166  			},
   167  			AllowUpgrade:         []string{"websocket"},
   168  			Timeout:              &emissaryv3alpha1.MillisecondDuration{Duration: time.Millisecond * 60000},
   169  			ConnectTimeout:       &emissaryv3alpha1.MillisecondDuration{Duration: time.Millisecond * 60000},
   170  			RemoveRequestHeaders: &requestHeadersToRemove,
   171  		},
   172  	}
   173  }
   174  
   175  func createOrUpdateExternalSecretSyncedObject(ctx context.Context, c client.Client, cluster *v1cluster.Cluster) error {
   176  	smSecretName := k8objectsutils.NameWithPrefix(constants.StoreName, cluster.ObjectMeta.Name)
   177  	member := fmt.Sprintf("serviceAccount:ext-sec-%s@%s.iam.gserviceaccount.com", uuid.FromUUID(cluster.ObjectMeta.Name).Hash(), cluster.Spec.ProjectID)
   178  
   179  	pMember := &iamAPI.IAMPolicyMember{
   180  		ObjectMeta: metav1.ObjectMeta{
   181  			Name:      fmt.Sprintf("essa-%s", smSecretName),
   182  			Namespace: cluster.ObjectMeta.Name,
   183  		},
   184  		TypeMeta: metav1.TypeMeta{
   185  			APIVersion: iamAPI.IAMPolicyMemberGVK.GroupVersion().String(),
   186  			Kind:       iamAPI.IAMPolicyMemberGVK.Kind,
   187  		},
   188  		Spec: iamAPI.IAMPolicyMemberSpec{
   189  			Member: &member,
   190  			ResourceRef: v1alpha1.IAMResourceRef{
   191  				APIVersion: secretmanagerAPI.SecretManagerSecretGVK.GroupVersion().String(),
   192  				Kind:       secretmanagerAPI.SecretManagerSecretGVK.Kind,
   193  				External:   fmt.Sprintf("projects/%s/secrets/%s", cluster.Spec.ProjectID, smSecretName),
   194  			},
   195  			Role: "roles/secretmanager.secretAccessor",
   196  		},
   197  	}
   198  	err := clientutils.CreateOrUpdatePolicyMember(ctx, c, pMember)
   199  	if err != nil {
   200  		return err
   201  	}
   202  
   203  	externalSecret := k8objectsutils.BuildExternalSecret(
   204  		cluster.Spec.ProjectID,
   205  		smSecretName,
   206  		constants.VPNNamespace,
   207  		constants.StoreName,
   208  		constants.WireguardSecretField,
   209  	)
   210  	prefix := fmt.Sprintf("%s-externalsecret", constants.StoreName)
   211  	syncedObject, err := k8objectsutils.BuildClusterSyncedObject(cluster, externalSecret, prefix)
   212  	if err != nil {
   213  		return err
   214  	}
   215  	return clientutils.CreateOrUpdateSyncedObject(ctx, c, syncedObject)
   216  }
   217  
   218  func removeExternalSecretSyncedObject(ctx context.Context, c client.Client, clusterEdgeID string) error {
   219  	syncedObjectName := fmt.Sprintf("%s-externalsecret-%s", constants.StoreName, clusterEdgeID)
   220  	key := client.ObjectKey{Namespace: clusterEdgeID, Name: syncedObjectName}
   221  	return removeSyncedObject(ctx, c, key)
   222  }
   223  
   224  func removeSyncedObject(ctx context.Context, c client.Client, key client.ObjectKey) error {
   225  	syncedObject := &v1alpha1syncedobject.SyncedObject{}
   226  	if err := c.Get(ctx, key, syncedObject); errors.IsNotFound(err) {
   227  		return nil
   228  	} else if err != nil {
   229  		return err
   230  	}
   231  	return c.Delete(ctx, syncedObject)
   232  }
   233  

View as plain text