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