From 0e76ff7ebdf26c14e7d38884a6378675cd89a493 Mon Sep 17 00:00:00 2001 From: nxtcoder17 Date: Sun, 12 May 2024 22:41:24 +0530 Subject: [PATCH 1/2] feat(infra): fixes global vpn connection peers, devices, and kloudlite cluster proxy --- .../internal/app/graph/generated/generated.go | 88 ++++++++++++++++++- .../internal/app/graph/model/models_gen.go | 3 +- apps/infra/internal/app/graph/schema.graphqls | 1 + .../internal/app/graph/schema.resolvers.go | 12 ++- .../globalvpndevice.graphqls | 2 + apps/infra/internal/domain/clusters.go | 72 ++++++++++++++- .../domain/global-vpn-cluster-connection.go | 28 ++++-- .../internal/domain/global-vpn-devices.go | 15 +++- apps/infra/internal/domain/global-vpn.go | 25 +++++- apps/infra/internal/domain/templates/embed.go | 3 +- ...pl => global-vpn-kloudlite-device.yml.tpl} | 80 +++++++++++------ apps/infra/internal/domain/templates/types.go | 9 +- apps/infra/internal/entities/cluster-group.go | 5 ++ .../field-constants/generated_constants.go | 14 ++- .../entities/global-vpn-connection.go | 10 ++- .../internal/entities/global-vpn-device.go | 2 + apps/infra/internal/env/env.go | 2 + 17 files changed, 316 insertions(+), 55 deletions(-) rename apps/infra/internal/domain/templates/{cluster-kube-proxy.yml.tpl => global-vpn-kloudlite-device.yml.tpl} (51%) diff --git a/apps/infra/internal/app/graph/generated/generated.go b/apps/infra/internal/app/graph/generated/generated.go index 439e922ca..370c5b0bf 100644 --- a/apps/infra/internal/app/graph/generated/generated.go +++ b/apps/infra/internal/app/graph/generated/generated.go @@ -629,6 +629,7 @@ type ComplexityRoot struct { GlobalVPNDevice struct { AccountName func(childComplexity int) int CreatedBy func(childComplexity int) int + CreationMethod func(childComplexity int) int CreationTime func(childComplexity int) int DisplayName func(childComplexity int) int GlobalVPNName func(childComplexity int) int @@ -4090,6 +4091,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.GlobalVPNDevice.CreatedBy(childComplexity), true + case "GlobalVPNDevice.creationMethod": + if e.complexity.GlobalVPNDevice.CreationMethod == nil { + break + } + + return e.complexity.GlobalVPNDevice.CreationMethod(childComplexity), true + case "GlobalVPNDevice.creationTime": if e.complexity.GlobalVPNDevice.CreationTime == nil { break @@ -8063,6 +8071,7 @@ input SearchGlobalVPNs { input SearchGlobalVPNDevices { text: MatchFilterIn + creationMethod: MatchFilterIn } input SearchClusterManagedService { @@ -9955,6 +9964,7 @@ input GlobalVPNIn { {Name: "../struct-to-graphql/globalvpndevice.graphqls", Input: `type GlobalVPNDevice @shareable { accountName: String! createdBy: Github__com___kloudlite___api___common__CreatedOrUpdatedBy! + creationMethod: String creationTime: Date! displayName: String! globalVPNName: String! @@ -9982,6 +9992,7 @@ type GlobalVPNDevicePaginatedRecords @shareable { } input GlobalVPNDeviceIn { + creationMethod: String displayName: String! globalVPNName: String! metadata: MetadataIn! @@ -27409,6 +27420,47 @@ func (ec *executionContext) fieldContext_GlobalVPNDevice_createdBy(ctx context.C return fc, nil } +func (ec *executionContext) _GlobalVPNDevice_creationMethod(ctx context.Context, field graphql.CollectedField, obj *entities.GlobalVPNDevice) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_GlobalVPNDevice_creationMethod(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.CreationMethod, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalOString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_GlobalVPNDevice_creationMethod(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "GlobalVPNDevice", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _GlobalVPNDevice_creationTime(ctx context.Context, field graphql.CollectedField, obj *entities.GlobalVPNDevice) (ret graphql.Marshaler) { fc, err := ec.fieldContext_GlobalVPNDevice_creationTime(ctx, field) if err != nil { @@ -28133,6 +28185,8 @@ func (ec *executionContext) fieldContext_GlobalVPNDeviceEdge_node(ctx context.Co return ec.fieldContext_GlobalVPNDevice_accountName(ctx, field) case "createdBy": return ec.fieldContext_GlobalVPNDevice_createdBy(ctx, field) + case "creationMethod": + return ec.fieldContext_GlobalVPNDevice_creationMethod(ctx, field) case "creationTime": return ec.fieldContext_GlobalVPNDevice_creationTime(ctx, field) case "displayName": @@ -41648,6 +41702,8 @@ func (ec *executionContext) fieldContext_Mutation_infra_createGlobalVPNDevice(ct return ec.fieldContext_GlobalVPNDevice_accountName(ctx, field) case "createdBy": return ec.fieldContext_GlobalVPNDevice_createdBy(ctx, field) + case "creationMethod": + return ec.fieldContext_GlobalVPNDevice_creationMethod(ctx, field) case "creationTime": return ec.fieldContext_GlobalVPNDevice_creationTime(ctx, field) case "displayName": @@ -41760,6 +41816,8 @@ func (ec *executionContext) fieldContext_Mutation_infra_updateGlobalVPNDevice(ct return ec.fieldContext_GlobalVPNDevice_accountName(ctx, field) case "createdBy": return ec.fieldContext_GlobalVPNDevice_createdBy(ctx, field) + case "creationMethod": + return ec.fieldContext_GlobalVPNDevice_creationMethod(ctx, field) case "creationTime": return ec.fieldContext_GlobalVPNDevice_creationTime(ctx, field) case "displayName": @@ -49960,6 +50018,8 @@ func (ec *executionContext) fieldContext_Query_infra_getGlobalVPNDevice(ctx cont return ec.fieldContext_GlobalVPNDevice_accountName(ctx, field) case "createdBy": return ec.fieldContext_GlobalVPNDevice_createdBy(ctx, field) + case "creationMethod": + return ec.fieldContext_GlobalVPNDevice_creationMethod(ctx, field) case "creationTime": return ec.fieldContext_GlobalVPNDevice_creationTime(ctx, field) case "displayName": @@ -56530,13 +56590,22 @@ func (ec *executionContext) unmarshalInputGlobalVPNDeviceIn(ctx context.Context, asMap[k] = v } - fieldsInOrder := [...]string{"displayName", "globalVPNName", "metadata"} + fieldsInOrder := [...]string{"creationMethod", "displayName", "globalVPNName", "metadata"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { continue } switch k { + case "creationMethod": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("creationMethod")) + data, err := ec.unmarshalOString2string(ctx, v) + if err != nil { + return it, err + } + it.CreationMethod = data case "displayName": var err error @@ -60555,7 +60624,7 @@ func (ec *executionContext) unmarshalInputSearchGlobalVPNDevices(ctx context.Con asMap[k] = v } - fieldsInOrder := [...]string{"text"} + fieldsInOrder := [...]string{"text", "creationMethod"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -60571,6 +60640,15 @@ func (ec *executionContext) unmarshalInputSearchGlobalVPNDevices(ctx context.Con return it, err } it.Text = data + case "creationMethod": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("creationMethod")) + data, err := ec.unmarshalOMatchFilterIn2ᚖgithubᚗcomᚋkloudliteᚋapiᚋpkgᚋreposᚐMatchFilter(ctx, v) + if err != nil { + return it, err + } + it.CreationMethod = data } } @@ -65671,6 +65749,8 @@ func (ec *executionContext) _GlobalVPNDevice(ctx context.Context, sel ast.Select if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } + case "creationMethod": + out.Values[i] = ec._GlobalVPNDevice_creationMethod(ctx, field, obj) case "creationTime": field := field @@ -74911,7 +74991,7 @@ func (ec *executionContext) marshalNfederation__Scope2ᚕᚕstringᚄ(ctx contex return ret } -func (ec *executionContext) unmarshalOAny2interface(ctx context.Context, v interface{}) (interface{}, error) { +func (ec *executionContext) unmarshalOAny2interface(ctx context.Context, v interface{}) (any, error) { if v == nil { return nil, nil } @@ -74919,7 +74999,7 @@ func (ec *executionContext) unmarshalOAny2interface(ctx context.Context, v inter return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalOAny2interface(ctx context.Context, sel ast.SelectionSet, v interface{}) graphql.Marshaler { +func (ec *executionContext) marshalOAny2interface(ctx context.Context, sel ast.SelectionSet, v any) graphql.Marshaler { if v == nil { return graphql.Null } diff --git a/apps/infra/internal/app/graph/model/models_gen.go b/apps/infra/internal/app/graph/model/models_gen.go index 9a54171d5..213db8b89 100644 --- a/apps/infra/internal/app/graph/model/models_gen.go +++ b/apps/infra/internal/app/graph/model/models_gen.go @@ -1456,7 +1456,8 @@ type SearchDomainEntry struct { } type SearchGlobalVPNDevices struct { - Text *repos.MatchFilter `json:"text,omitempty"` + Text *repos.MatchFilter `json:"text,omitempty"` + CreationMethod *repos.MatchFilter `json:"creationMethod,omitempty"` } type SearchGlobalVPNs struct { diff --git a/apps/infra/internal/app/graph/schema.graphqls b/apps/infra/internal/app/graph/schema.graphqls index ffd9898d2..ea2bfe809 100644 --- a/apps/infra/internal/app/graph/schema.graphqls +++ b/apps/infra/internal/app/graph/schema.graphqls @@ -32,6 +32,7 @@ input SearchGlobalVPNs { input SearchGlobalVPNDevices { text: MatchFilterIn + creationMethod: MatchFilterIn } input SearchClusterManagedService { diff --git a/apps/infra/internal/app/graph/schema.resolvers.go b/apps/infra/internal/app/graph/schema.resolvers.go index d4db3ba32..c628a0fae 100644 --- a/apps/infra/internal/app/graph/schema.resolvers.go +++ b/apps/infra/internal/app/graph/schema.resolvers.go @@ -8,6 +8,7 @@ import ( "context" "encoding/base64" "fmt" + "github.com/kloudlite/api/pkg/errors" "github.com/kloudlite/api/apps/infra/internal/app/graph/generated" @@ -47,7 +48,7 @@ func (r *clusterResolver) AdminKubeconfig(ctx context.Context, obj *entities.Clu // ClusterDNSSuffix is the resolver for the clusterDNSSuffix field. func (r *clusterResolver) ClusterDNSSuffix(ctx context.Context, obj *entities.Cluster) (string, error) { - return fmt.Sprintf("%s.local", obj.Name), nil + return fmt.Sprintf("%s.local", obj.Name), nil } // WireguardConfig is the resolver for the wireguardConfig field. @@ -553,6 +554,9 @@ func (r *queryResolver) InfraListGlobalVPNDevices(ctx context.Context, gvpn stri if search.Text != nil { filter["metadata.name"] = *search.Text } + if search.CreationMethod != nil { + filter["creationMethod"] = *search.CreationMethod + } } records, err := r.Domain.ListGlobalVPNDevice(ictx, gvpn, filter, *pagination) @@ -1100,8 +1104,10 @@ func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResol // Query returns generated.QueryResolver implementation. func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } -type mutationResolver struct{ *Resolver } -type queryResolver struct{ *Resolver } +type ( + mutationResolver struct{ *Resolver } + queryResolver struct{ *Resolver } +) // !!! WARNING !!! // The code below was going to be deleted when updating resolvers. It has been copied here so you have diff --git a/apps/infra/internal/app/graph/struct-to-graphql/globalvpndevice.graphqls b/apps/infra/internal/app/graph/struct-to-graphql/globalvpndevice.graphqls index 43dfa4e91..3af2972fb 100644 --- a/apps/infra/internal/app/graph/struct-to-graphql/globalvpndevice.graphqls +++ b/apps/infra/internal/app/graph/struct-to-graphql/globalvpndevice.graphqls @@ -1,6 +1,7 @@ type GlobalVPNDevice @shareable { accountName: String! createdBy: Github__com___kloudlite___api___common__CreatedOrUpdatedBy! + creationMethod: String creationTime: Date! displayName: String! globalVPNName: String! @@ -28,6 +29,7 @@ type GlobalVPNDevicePaginatedRecords @shareable { } input GlobalVPNDeviceIn { + creationMethod: String displayName: String! globalVPNName: String! metadata: MetadataIn! diff --git a/apps/infra/internal/domain/clusters.go b/apps/infra/internal/domain/clusters.go index 95f6f4c0d..b9ef414d0 100644 --- a/apps/infra/internal/domain/clusters.go +++ b/apps/infra/internal/domain/clusters.go @@ -113,8 +113,6 @@ func (d *domain) GetClusterAdminKubeconfig(ctx InfraContext, clusterName string) func (d *domain) applyCluster(ctx InfraContext, cluster *entities.Cluster) error { addTrackingId(&cluster.Cluster, cluster.Id) return d.applyK8sResource(ctx, &cluster.Cluster, cluster.RecordVersion) - - // TODO: create cluster connection and apply to target cluster } func (d *domain) CreateCluster(ctx InfraContext, cluster entities.Cluster) (*entities.Cluster, error) { @@ -374,6 +372,56 @@ func (d *domain) CreateCluster(ctx InfraContext, cluster entities.Cluster) (*ent return nCluster, nil } +/* +TODO: + - create a specific device for each global VPN reserved for kloudlite + - need to use that device as a kube-proxy to all the clusters + - we can read their logs, and everything on demand +*/ + +func (d *domain) syncKloudliteDeviceOnCluster(ctx InfraContext, gvpnName string) error { + // 1. parse deployment template + b, err := templates.Read(templates.GlobalVPNKloudliteDeviceTemplate) + if err != nil { + return errors.NewE(err) + } + accNs, err := d.getAccNamespace(ctx) + if err != nil { + return errors.NewE(err) + } + + gv, err := d.findGlobalVPN(ctx, gvpnName) + if err != nil { + return err + } + + if gv.KloudliteDevice.Name == "" { + return nil + } + + // 2. Grab wireguard config from that device + wgConfig, err := d.getGlobalVPNDeviceWgConfig(ctx, gv.Name, gv.KloudliteDevice.Name) + if err != nil { + return err + } + + deploymentBytes, err := templates.ParseBytes(b, templates.GVPNKloudliteDeviceTemplateVars{ + Name: fmt.Sprintf("kloudlite-device-proxy-%s", gv.Name), + Namespace: accNs, + WgConfig: wgConfig, + KubeReverseProxyImage: d.env.GlobalVPNKubeReverseProxyImage, + }) + if err != nil { + return err + } + + if err := d.k8sClient.ApplyYAML(ctx, deploymentBytes); err != nil { + return errors.NewE(err) + } + + return nil +} + func (d *domain) applyHelmKloudliteAgent(ctx InfraContext, clusterToken string, cluster *entities.Cluster) error { b, err := templates.Read(templates.HelmKloudliteAgent) if err != nil { @@ -522,6 +570,26 @@ func (d *domain) GetCluster(ctx InfraContext, name string) (*entities.Cluster, e c, err := d.findCluster(ctx, name) if err != nil { + if errors.Is(err, ErrClusterNotFound) { + byokCluster, err := d.findBYOKCluster(ctx, name) + if err != nil { + return nil, err + } + return &entities.Cluster{ + Cluster: clustersv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: byokCluster.Name, + }, + Spec: clustersv1.ClusterSpec{ + ClusterServiceCIDR: byokCluster.ClusterSvcCIDR, + PublicDNSHost: "", + }, + }, + ResourceMetadata: byokCluster.ResourceMetadata, + AccountName: byokCluster.AccountName, + GlobalVPN: &byokCluster.GlobalVPN, + }, nil + } return nil, errors.NewE(err) } diff --git a/apps/infra/internal/domain/global-vpn-cluster-connection.go b/apps/infra/internal/domain/global-vpn-cluster-connection.go index 2a4c785ca..7e769d13f 100644 --- a/apps/infra/internal/domain/global-vpn-cluster-connection.go +++ b/apps/infra/internal/domain/global-vpn-cluster-connection.go @@ -20,6 +20,10 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +const ( + gvpnConnectionDeviceMethod = "gvpn-connection" +) + func (d *domain) getGlobalVPNConnectionPeers(vpns []*entities.GlobalVPNConnection) ([]wgv1.Peer, error) { peers := make([]wgv1.Peer, 0, len(vpns)) for _, c := range vpns { @@ -176,12 +180,22 @@ func (d *domain) createGlobalVPNConnection(ctx InfraContext, gvpnConn entities.G gvpnConn.SyncStatus = t.GenSyncStatus(t.SyncActionApply, 0) - gatewayAddr, err := d.claimNextFreeDeviceIP(ctx, fmt.Sprintf("%s-cluster-gateway", gvpnConn.ClusterName), gvpnConn.Name) + gvpnDevice, err := d.createGlobalVPNDevice(ctx, entities.GlobalVPNDevice{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("cluster-gateway-%s", gvpnConn.ClusterName), + }, + AccountName: ctx.AccountName, + GlobalVPNName: gvpnConn.Name, + CreationMethod: gvpnConnectionDeviceMethod, + }) if err != nil { return nil, err } - gvpnConn.GatewayIPAddr = gatewayAddr + gvpnConn.DeviceRef = entities.GlobalVPNConnDeviceRef{ + Name: gvpnDevice.Name, + IPAddr: gvpnDevice.IPAddr, + } gv, err := d.gvpnConnRepo.Create(ctx, &gvpnConn) if err != nil { @@ -208,7 +222,7 @@ func (d *domain) deleteGlobalVPNConnection(ctx InfraContext, clusterName string, return errors.Newf("no global vpn connection with name (%s) not found, for cluster (%s)", gvpnName, clusterName) } - if err := d.deleteGlobalVPNDevice(ctx, gvpnName, fmt.Sprintf("%s-cluster-gateway", gvpnConn.ClusterName)); err != nil { + if err := d.deleteGlobalVPNDevice(ctx, gvpnName, gvpnConn.DeviceRef.Name); err != nil { return errors.NewE(err) } @@ -270,7 +284,7 @@ func (d *domain) applyGlobalVPNConnection(ctx InfraContext, gvpn *entities.Globa Namespace: gvpn.Spec.WgRef.Namespace, }, StringData: map[string]string{ - "ip": gvpn.GatewayIPAddr, + "ip": gvpn.DeviceRef.IPAddr, }, }, 0); err != nil { return err @@ -325,7 +339,7 @@ func (d *domain) OnGlobalVPNConnectionUpdateMessage(ctx InfraContext, clusterNam return errors.NewE(err) } - //INFO: BYOK cluster does not have any status update message + // INFO: BYOK cluster does not have any status update message if d.isBYOKCluster(ctx, xconn.ClusterName) { if _, err := d.byokClusterRepo.PatchOne(ctx, entities.UniqueBYOKClusterFilter(ctx.AccountName, clusterName), repos.Document{ fc.SyncStatusState: t.SyncStateUpdatedAtAgent, @@ -362,6 +376,10 @@ func (d *domain) OnGlobalVPNConnectionUpdateMessage(ctx InfraContext, clusterNam return errors.NewE(err) } + if err := d.syncKloudliteDeviceOnCluster(ctx, xconn.GlobalVPNName); err != nil { + return errors.NewE(err) + } + d.resourceEventPublisher.PublishResourceEvent(ctx, clusterName, ResourceTypeClusterConnection, ugvpn.Name, PublishUpdate) return nil } diff --git a/apps/infra/internal/domain/global-vpn-devices.go b/apps/infra/internal/domain/global-vpn-devices.go index c7a97740c..f65d91e30 100644 --- a/apps/infra/internal/domain/global-vpn-devices.go +++ b/apps/infra/internal/domain/global-vpn-devices.go @@ -112,7 +112,7 @@ func (d *domain) deleteGlobalVPNDevice(ctx InfraContext, gvpn string, deviceName } func (d *domain) DeleteGlobalVPNDevice(ctx InfraContext, gvpn string, deviceName string) error { - return d.deleteGlobalVPNConnection(ctx, gvpn, deviceName) + return d.deleteGlobalVPNDevice(ctx, gvpn, deviceName) } func (d *domain) ListGlobalVPNDevice(ctx InfraContext, gvpn string, search map[string]repos.MatchFilter, pagination repos.CursorPagination) (*repos.PaginatedRecord[*entities.GlobalVPNDevice], error) { @@ -124,6 +124,10 @@ func (d *domain) ListGlobalVPNDevice(ctx InfraContext, gvpn string, search map[s } func (d *domain) CreateGlobalVPNDevice(ctx InfraContext, gvpnDevice entities.GlobalVPNDevice) (*entities.GlobalVPNDevice, error) { + return d.createGlobalVPNDevice(ctx, gvpnDevice) +} + +func (d *domain) createGlobalVPNDevice(ctx InfraContext, gvpnDevice entities.GlobalVPNDevice) (*entities.GlobalVPNDevice, error) { gvpnDevice.AccountName = ctx.AccountName gvpnDevice.CreatedBy = common.CreatedOrUpdatedBy{ UserId: ctx.UserId, @@ -162,8 +166,9 @@ func (d *domain) CreateGlobalVPNDevice(ctx InfraContext, gvpnDevice entities.Glo func (d *domain) buildPeersFromGlobalVPNDevices(ctx InfraContext, gvpn string) (publicPeers []wgv1.Peer, privatePeers []wgv1.Peer, err error) { devices, err := d.gvpnDevicesRepo.Find(ctx, repos.Query{ Filter: map[string]any{ - fc.AccountName: ctx.AccountName, - fc.GlobalVPNDeviceGlobalVPNName: gvpn, + fc.AccountName: ctx.AccountName, + fc.GlobalVPNDeviceGlobalVPNName: gvpn, + fc.GlobalVPNDeviceCreationMethod: map[string]any{"$ne": gvpnConnectionDeviceMethod}, }, }) if err != nil { @@ -200,6 +205,10 @@ func (d *domain) GetGlobalVPNDevice(ctx InfraContext, gvpn string, gvpnDevice st } func (d *domain) GetGlobalVPNDeviceWgConfig(ctx InfraContext, gvpn string, gvpnDevice string) (string, error) { + return d.getGlobalVPNDeviceWgConfig(ctx, gvpn, gvpnDevice) +} + +func (d *domain) getGlobalVPNDeviceWgConfig(ctx InfraContext, gvpn string, gvpnDevice string) (string, error) { device, err := d.findGlobalVPNDevice(ctx, gvpn, gvpnDevice) if err != nil { return "", err diff --git a/apps/infra/internal/domain/global-vpn.go b/apps/infra/internal/domain/global-vpn.go index 2d2db1361..315d21b23 100644 --- a/apps/infra/internal/domain/global-vpn.go +++ b/apps/infra/internal/domain/global-vpn.go @@ -34,7 +34,30 @@ func (d *domain) createGlobalVPN(ctx InfraContext, gvpn entities.GlobalVPN) (*en gvpn.WgInterface = "kl0" } - return d.gvpnRepo.Create(ctx, &gvpn) + gv, err := d.gvpnRepo.Create(ctx, &gvpn) + if err != nil { + return nil, err + } + + device, err := d.createGlobalVPNDevice(ctx, entities.GlobalVPNDevice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kloudlite-platform-device", + }, + ResourceMetadata: common.ResourceMetadata{ + DisplayName: "kloudlite-platform-device", + CreatedBy: common.CreatedOrUpdatedByKloudlite, + LastUpdatedBy: common.CreatedOrUpdatedByKloudlite, + }, + AccountName: ctx.AccountName, + GlobalVPNName: gv.Name, + PublicEndpoint: nil, + CreationMethod: "kloudlite-platform-auto-create", + }) + if err != nil { + return nil, err + } + + return d.gvpnRepo.PatchById(ctx, gv.Id, repos.Document{fc.GlobalVPNKloudliteDeviceName: device.Name, fc.GlobalVPNKloudliteDeviceIpAddr: device.IPAddr}) } func (d *domain) ensureGlobalVPN(ctx InfraContext, gvpnName string) (*entities.GlobalVPN, error) { diff --git a/apps/infra/internal/domain/templates/embed.go b/apps/infra/internal/domain/templates/embed.go index cd4d68e66..1f520eeec 100644 --- a/apps/infra/internal/domain/templates/embed.go +++ b/apps/infra/internal/domain/templates/embed.go @@ -13,7 +13,8 @@ var templatesDir embed.FS type templateFile string const ( - HelmKloudliteAgent templateFile = "./helm-charts-kloudlite-agent.yml.tpl" + HelmKloudliteAgent templateFile = "./helm-charts-kloudlite-agent.yml.tpl" + GlobalVPNKloudliteDeviceTemplate templateFile = "./global-vpn-kloudlite-device.yml.tpl" ) func Read(t templateFile) ([]byte, error) { diff --git a/apps/infra/internal/domain/templates/cluster-kube-proxy.yml.tpl b/apps/infra/internal/domain/templates/global-vpn-kloudlite-device.yml.tpl similarity index 51% rename from apps/infra/internal/domain/templates/cluster-kube-proxy.yml.tpl rename to apps/infra/internal/domain/templates/global-vpn-kloudlite-device.yml.tpl index 2af9492f8..0a2034ad4 100644 --- a/apps/infra/internal/domain/templates/cluster-kube-proxy.yml.tpl +++ b/apps/infra/internal/domain/templates/global-vpn-kloudlite-device.yml.tpl @@ -1,29 +1,33 @@ -{{- /*gotype: github.com/kloudlite/api/apps/infra/internal/domain/templates.ClusterKubeProxyVars*/ -}} +{{- /*gotype: github.com/kloudlite/api/apps/infra/internal/domain/templates.GVPNKloudliteDeviceVars*/ -}} {{ with . }} + +{{- $namespace := .Namespace }} +{{- $klDeviceWgSecretName := "kl-device-wg-secret-name" }} + --- apiVersion: v1 kind: Namespace metadata: - name: {{.Namespace | squote}} + name: {{$namespace}} --- apiVersion: v1 kind: Secret metadata: - name: kloudlite-gvpn-device-config - namespace: {{.Namespace | squote}} + name: {{$klDeviceWgSecretName | squote}} + namespace: {{$namespace}} data: - wg0.conf: {{.KloudliteDeviceWgConfig | b64enc }} + wg0.conf: {{.WgConfig | b64enc }} --- apiVersion: apps/v1 kind: Deployment metadata: - name: kube-access-globalvpn - namespace: {{.Namespace | squote}} + name: {{.Name | squote}} + namespace: {{$namespace}} spec: replicas: 1 selector: - matchLabels: - app: app + matchLabels: &labels + app: {{.Name | squote}} strategy: rollingUpdate: maxSurge: 25% @@ -31,8 +35,9 @@ spec: type: RollingUpdate template: metadata: - labels: - app: app + labels: *labels + annotations: + "secret-ref": "{{.WgConfig | b64enc | sha256sum}}" spec: initContainers: - name: init @@ -47,6 +52,7 @@ spec: add: - NET_ADMIN - SYS_MODULE + containers: - image: linuxserver/wireguard imagePullPolicy: Always @@ -65,22 +71,40 @@ spec: privileged: true volumeMounts: - mountPath: /config/wg_confs/wg0.conf - name: kloudlite-gvpn-device-config + name: wg-config subPath: wg0.conf terminationMessagePath: /dev/termination-log terminationMessagePolicy: File - - image: ghcr.io/kloudlite/hub/socat:latest - command: - - sh - - -c - - |+ - (socat -dd tcp4-listen:8080,fork,reuseaddr tcp4:kubectl-proxy.{{.Namespace}}.svc.example-test.local:8080 2>&1 | grep -iE --line-buffered 'listening|exiting') & - pid=$! - trap "kill -9 $pid" EXIT SIGINT SIGTERM - wait $pid - imagePullPolicy: Always - name: app + {{- /* - name: debug */}} + {{- /* image: ghcr.io/kloudlite/hub/socat:latest */}} + {{- /* imagePullPolicy: Always */}} + {{- /* resources: */}} + {{- /* limits: */}} + {{- /* cpu: 100m */}} + {{- /* memory: 100Mi */}} + {{- /* requests: */}} + {{- /* cpu: 100m */}} + {{- /* memory: 100Mi + {{- /* command: */}} + {{- /* - sh */}} + {{- /* - -c */}} + {{- /* - |+ */}} + {{- /* (socat -dd tcp4-listen:8080,fork,reuseaddr tcp4:kubectl-proxy.{{.Namespace}}.svc.example-test.local:8080 2>&1 | grep -iE --line-buffered 'listening|exiting') & */}} + {{- /* pid=$! */}} + {{- /**/}} + {{- /* trap "kill -9 $pid" EXIT SIGINT SIGTERM */}} + {{- /* wait $pid */}} + + - name: kube-reverse-proxy + image: {{.KubeReverseProxyImage}} + args: + - --addr + - ":8080" + - --proxy-addr + # this %s will be replaced with real cluster name by reverse proxy + - {{ printf "kubectl-proxy.kloudlite.svc.{{.CLUSTER_NAME}}.local:8080" }} + imagePullPolicy: "IfNotPresent" resources: limits: cpu: 100m @@ -96,19 +120,19 @@ spec: secret: defaultMode: 420 items: - - key: wg0.conf + - key: "wg0.conf" path: wg0.conf - secretName: wg-config + secretName: {{$klDeviceWgSecretName | squote}} --- apiVersion: v1 kind: Service metadata: - name: kube-access - namespace: test-kube-access-globalvpn + name: {{.Name | squote }} + namespace: {{.Namespace | squote}} spec: selector: - app: app + app: {{.Name | squote}} ports: - name: p-8080 port: 8080 diff --git a/apps/infra/internal/domain/templates/types.go b/apps/infra/internal/domain/templates/types.go index 6970b9fbb..0a14d320a 100644 --- a/apps/infra/internal/domain/templates/types.go +++ b/apps/infra/internal/domain/templates/types.go @@ -1,6 +1,9 @@ package templates -type ClusterKubeProxyVars struct { - Namespace string - KloudliteDeviceWgConfig string +type GVPNKloudliteDeviceTemplateVars struct { + Name string + Namespace string + WgConfig string + + KubeReverseProxyImage string } diff --git a/apps/infra/internal/entities/cluster-group.go b/apps/infra/internal/entities/cluster-group.go index 20adc76d8..30128c973 100644 --- a/apps/infra/internal/entities/cluster-group.go +++ b/apps/infra/internal/entities/cluster-group.go @@ -39,6 +39,11 @@ type GlobalVPN struct { // Peers []Peer `json:"peers" graphql:"noinput"` AccountName string `json:"accountName" graphql:"noinput"` + + KloudliteDevice struct { + Name string `json:"name"` + IPAddr string `json:"ipAddr"` + } `json:"kloudliteDevice"` } func (c *GlobalVPN) GetDisplayName() string { diff --git a/apps/infra/internal/entities/field-constants/generated_constants.go b/apps/infra/internal/entities/field-constants/generated_constants.go index 14d0764a1..18c9ecb9e 100644 --- a/apps/infra/internal/entities/field-constants/generated_constants.go +++ b/apps/infra/internal/entities/field-constants/generated_constants.go @@ -214,17 +214,28 @@ const ( const ( GlobalVPNCIDR = "CIDR" GlobalVPNAllocatableCIDRSuffix = "allocatableCIDRSuffix" + GlobalVPNKloudliteDevice = "kloudliteDevice" + GlobalVPNKloudliteDeviceIpAddr = "kloudliteDevice.ipAddr" + GlobalVPNKloudliteDeviceName = "kloudliteDevice.name" GlobalVPNNumAllocatedClusterCIDRs = "numAllocatedClusterCIDRs" GlobalVPNNumAllocatedDevices = "numAllocatedDevices" GlobalVPNNumReservedIPsForNonClusterUse = "numReservedIPsForNonClusterUse" GlobalVPNWgInterface = "wgInterface" ) +// constant vars generated for struct GlobalVPNConnDeviceRef +const ( + GlobalVPNConnDeviceRefIpAddr = "ipAddr" + GlobalVPNConnDeviceRefName = "name" +) + // constant vars generated for struct GlobalVPNConnection const ( GlobalVPNConnectionClusterPublicEndpoint = "clusterPublicEndpoint" GlobalVPNConnectionClusterSvcCIDR = "clusterSvcCIDR" - GlobalVPNConnectionGatewayIPAddr = "gatewayIPAddr" + GlobalVPNConnectionDeviceRef = "deviceRef" + GlobalVPNConnectionDeviceRefIpAddr = "deviceRef.ipAddr" + GlobalVPNConnectionDeviceRefName = "deviceRef.name" GlobalVPNConnectionGlobalVPNName = "globalVPNName" GlobalVPNConnectionParsedWgParams = "parsedWgParams" GlobalVPNConnectionParsedWgParamsDnsServer = "parsedWgParams.dnsServer" @@ -251,6 +262,7 @@ const ( // constant vars generated for struct GlobalVPNDevice const ( + GlobalVPNDeviceCreationMethod = "creationMethod" GlobalVPNDeviceGlobalVPNName = "globalVPNName" GlobalVPNDeviceIpAddr = "ipAddr" GlobalVPNDevicePrivateKey = "privateKey" diff --git a/apps/infra/internal/entities/global-vpn-connection.go b/apps/infra/internal/entities/global-vpn-connection.go index caebaa035..4b71525cc 100644 --- a/apps/infra/internal/entities/global-vpn-connection.go +++ b/apps/infra/internal/entities/global-vpn-connection.go @@ -10,6 +10,11 @@ import ( "github.com/kloudlite/operator/pkg/operator" ) +type GlobalVPNConnDeviceRef struct { + Name string `json:"name"` + IPAddr string `json:"ipAddr"` +} + type GlobalVPNConnection struct { repos.BaseEntity `json:",inline" graphql:"noinput"` @@ -24,11 +29,10 @@ type GlobalVPNConnection struct { ClusterSvcCIDR string `json:"clusterSvcCIDR" graphql:"noinput"` ClusterPublicEndpoint string `json:"clusterPublicEndpoint" graphql:"noinput"` - GatewayIPAddr string `json:"gatewayIPAddr" graphql:"ignore"` + DeviceRef GlobalVPNConnDeviceRef `json:"deviceRef" graphql:"noinput"` ParsedWgParams *wgv1.WgParams `json:"parsedWgParams" graphql:"ignore"` - - SyncStatus t.SyncStatus `json:"syncStatus" graphql:"noinput"` + SyncStatus t.SyncStatus `json:"syncStatus" graphql:"noinput"` } func (c *GlobalVPNConnection) GetDisplayName() string { diff --git a/apps/infra/internal/entities/global-vpn-device.go b/apps/infra/internal/entities/global-vpn-device.go index df95bb3d6..e65a24061 100644 --- a/apps/infra/internal/entities/global-vpn-device.go +++ b/apps/infra/internal/entities/global-vpn-device.go @@ -20,6 +20,8 @@ type GlobalVPNDevice struct { // Only needs to be set, if vpn device has a public IP PublicEndpoint *string `json:"publicEndpoint,omitempty" graphql:"noinput"` + CreationMethod string `json:"creationMethod,omitempty"` + IPAddr string `json:"ipAddr" graphql:"noinput"` PrivateKey string `json:"privateKey" graphql:"noinput"` diff --git a/apps/infra/internal/env/env.go b/apps/infra/internal/env/env.go index ea8338bc1..bc49f815e 100644 --- a/apps/infra/internal/env/env.go +++ b/apps/infra/internal/env/env.go @@ -50,6 +50,8 @@ type Env struct { IsDev bool KubernetesApiProxy string `env:"KUBERNETES_API_PROXY"` + + GlobalVPNKubeReverseProxyImage string `env:"GLOBAL_VPN_KUBE_REVERSE_PROXY_IMAGE" required:"true"` } func LoadEnv() (*Env, error) { From 8a920e72591fa308e1eaa2595b86ea7e6a850d3a Mon Sep 17 00:00:00 2001 From: nxtcoder17 Date: Sun, 12 May 2024 22:47:58 +0530 Subject: [PATCH 2/2] fix: fixes observability api logs to global vpn kubectl proxy --- .tools/nvim/__http__/console/apps.graphql.yml | 4 +- .../infra/global-vpn-devices.graphql.yml | 2 +- apps/observability/internal/app/app.go | 9 +-- apps/websocket-server/internal/domain/main.go | 12 +--- .../internal/domain/observability-logs.go | 4 +- cmd/global-vpn-kube-proxy/Dockerfile | 5 ++ cmd/global-vpn-kube-proxy/Taskfile.yml | 19 ++++++ cmd/global-vpn-kube-proxy/client/main.go | 37 +++++++++++ cmd/global-vpn-kube-proxy/main.go | 66 +++++++++++++++++++ tools/README.md | 0 tools/global-vpn-kube-proxy/main.go | 0 11 files changed, 137 insertions(+), 21 deletions(-) create mode 100644 cmd/global-vpn-kube-proxy/Dockerfile create mode 100644 cmd/global-vpn-kube-proxy/Taskfile.yml create mode 100644 cmd/global-vpn-kube-proxy/client/main.go create mode 100644 cmd/global-vpn-kube-proxy/main.go delete mode 100644 tools/README.md delete mode 100644 tools/global-vpn-kube-proxy/main.go diff --git a/.tools/nvim/__http__/console/apps.graphql.yml b/.tools/nvim/__http__/console/apps.graphql.yml index c5c909fe8..ba6348511 100644 --- a/.tools/nvim/__http__/console/apps.graphql.yml +++ b/.tools/nvim/__http__/console/apps.graphql.yml @@ -97,9 +97,7 @@ variables: name: "{{.name}}" spec: services: - - type: tcp - port: 80 - targetPort: 80 + - port: 80 containers: - name: main # image: kong/httpbin diff --git a/.tools/nvim/__http__/infra/global-vpn-devices.graphql.yml b/.tools/nvim/__http__/infra/global-vpn-devices.graphql.yml index 8e48a0c2b..89bdbbbc6 100644 --- a/.tools/nvim/__http__/infra/global-vpn-devices.graphql.yml +++ b/.tools/nvim/__http__/infra/global-vpn-devices.graphql.yml @@ -1,7 +1,7 @@ --- global: gvpn: "default" - deviceName: "first-device" + deviceName: "kloudlite-platform-device" # deviceName: "second-device" --- label: "Create GlobalVPN Device" diff --git a/apps/observability/internal/app/app.go b/apps/observability/internal/app/app.go index 93d2e8f17..f75ad05da 100644 --- a/apps/observability/internal/app/app.go +++ b/apps/observability/internal/app/app.go @@ -149,12 +149,9 @@ var Module = fx.Module( if !strings.HasPrefix(trackingId, "clus-") { cfg := &rest.Config{ - Host: fmt.Sprintf("kubectl-proxy.%s.svc.cluster.local", accountName), - WrapTransport: func(rt http.RoundTripper) http.RoundTripper { - return httpServer.NewRoundTripperWithHeaders(rt, map[string][]string{ - "kloudlite-cluster": []string{clusterName}, - }) - }, + Host: fmt.Sprintf("http://kloudlite-device-proxy-%s.kl-account-%s.svc.cluster.local:8080/clusters/%s", "default", accountName, clusterName), + // Host: fmt.Sprintf("http://kube-access.test-kube-access-globalvpn.svc.cluster.local:8080/clusters/%s", clusterName), + // Host: fmt.Sprintf("http://kloudlite-device-proxy-default.kl-%s.svc.cluster.local:8080/clusters/%s", accountName, clusterName), } //out, err := infraCli.GetClusterKubeconfig(r.Context(), &infra.GetClusterIn{ diff --git a/apps/websocket-server/internal/domain/main.go b/apps/websocket-server/internal/domain/main.go index 65270eac8..692901706 100644 --- a/apps/websocket-server/internal/domain/main.go +++ b/apps/websocket-server/internal/domain/main.go @@ -33,7 +33,7 @@ func (d *domain) HandleWebSocket(ctx context.Context, c *websocket.Conn) error { if c != nil { mu.Lock() if err := c.WriteJSON(msg); err != nil { - d.logger.Warnf("websocket write: %w", err) + d.logger.Warnf("websocket write, error: %v", err) } mu.Unlock() return nil @@ -46,7 +46,7 @@ func (d *domain) HandleWebSocket(ctx context.Context, c *websocket.Conn) error { if c != nil { mu.Lock() if err := c.WriteMessage(websocket.TextMessage, b); err != nil { - d.logger.Warnf("websocket write: %w", err) + d.logger.Warnf("websocket write, error: %v", err) } mu.Unlock() return nil @@ -65,12 +65,6 @@ func (d *domain) HandleWebSocket(ctx context.Context, c *websocket.Conn) error { logsSubscriptions := make(map[string]LogSubscriptionCtx) - // disconnect := func() error { - // fmt.Println("-----DISCONNECTED-----") - // // write(`{"message": "CLOSING"}`) - // return c.Close() - // } - closed := false c.SetCloseHandler(func(_ int, _ string) error { closed = true @@ -79,7 +73,7 @@ func (d *domain) HandleWebSocket(ctx context.Context, c *websocket.Conn) error { defer func() { if err := c.Close(); err != nil { - d.logger.Warnf("websocket close: %w", err) + d.logger.Warnf("websocket close, error: %v", err) } for _, v := range logsSubscriptions { diff --git a/apps/websocket-server/internal/domain/observability-logs.go b/apps/websocket-server/internal/domain/observability-logs.go index 0e6b26092..b0a635d65 100644 --- a/apps/websocket-server/internal/domain/observability-logs.go +++ b/apps/websocket-server/internal/domain/observability-logs.go @@ -23,8 +23,6 @@ type LogSubscriptionCtx struct { } func (d *domain) handleObservabilityLogsMsg(ctx types.Context, subscriptions map[string]LogSubscriptionCtx, msgAny map[string]any) error { - // log := d.logger - var msg logs.Message b, err := json.Marshal(msgAny) if err != nil { @@ -51,6 +49,7 @@ func (d *domain) handleObservabilityLogsMsg(ctx types.Context, subscriptions map tpk := logs.LogSubsId(msg.Spec, d.env.LogsStreamName) d.logger.Debugf("tpk: %s", tpk) + d.logger.Infof("subscribing to logs for %s", msg.Spec.TrackingId) utils.WriteInfo(ctx, "subscribed to logs", msg.Id, types.ForLogs) nctx, cf := context.WithCancel(ctx.Context) @@ -127,6 +126,7 @@ func (d *domain) handleObservabilityLogsMsg(ctx types.Context, subscriptions map case logs.EventUnsubscribe: { + d.logger.Infof("unsubscribing to logs for %s", msg.Spec.TrackingId) utils.WriteInfo(ctx, "[logs] subscription cancelled for ", msg.Id, types.ForLogs) if v, ok := subscriptions[hash]; ok { v.CancelFunc() diff --git a/cmd/global-vpn-kube-proxy/Dockerfile b/cmd/global-vpn-kube-proxy/Dockerfile new file mode 100644 index 000000000..d8e0824dd --- /dev/null +++ b/cmd/global-vpn-kube-proxy/Dockerfile @@ -0,0 +1,5 @@ +#syntax=docker/dockerfile:1 +FROM --platform=$TARGETPLATFORM cgr.dev/chainguard/static:latest-glibc +ARG BINARY +COPY --chown=1001 $BINARY ./global-vpn-kube-proxy +ENTRYPOINT ["./global-vpn-kube-proxy"] diff --git a/cmd/global-vpn-kube-proxy/Taskfile.yml b/cmd/global-vpn-kube-proxy/Taskfile.yml new file mode 100644 index 000000000..0605ef2ca --- /dev/null +++ b/cmd/global-vpn-kube-proxy/Taskfile.yml @@ -0,0 +1,19 @@ +version: 3 + +includes: + go: ../../.tools/taskfiles/go-build.yml + +tasks: + build: + cmds: + - task: go:build + vars: + Out: ./bin/global-vpn-kube-proxy + + container:build-and-push: + preconditions: + - sh: '[[ -n "{{.Image}}" ]]' + msg: "var Image is not set" + cmds: + - task build upx={{.upx}} + - docker buildx build --platform linux/amd64,linux/arm64 --output=type=image,compression=zstd,force-compression=true,compression-level=14,push=true --build-arg BINARY="./bin/global-vpn-kube-proxy" -t {{.Image}} . diff --git a/cmd/global-vpn-kube-proxy/client/main.go b/cmd/global-vpn-kube-proxy/client/main.go new file mode 100644 index 000000000..cb1b7dd58 --- /dev/null +++ b/cmd/global-vpn-kube-proxy/client/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "context" + "fmt" + "log" + + "github.com/kloudlite/api/pkg/k8s" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func main() { + cfg := &rest.Config{ + Host: "http://localhost:8082/proxy", + } + + log.Print(cfg.String()) + + var err error + kcli, err := k8s.NewClient(cfg, nil) + if err != nil { + panic(err) + } + + var pods corev1.PodList + if err := kcli.List(context.TODO(), &pods, &client.ListOptions{ + Namespace: "kloudlite", + }); err != nil { + panic(err) + } + + for i := range pods.Items { + fmt.Println(pods.Items[i].Name) + } +} diff --git a/cmd/global-vpn-kube-proxy/main.go b/cmd/global-vpn-kube-proxy/main.go new file mode 100644 index 000000000..6b3146d09 --- /dev/null +++ b/cmd/global-vpn-kube-proxy/main.go @@ -0,0 +1,66 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + "net/http/httputil" + "strings" + "time" +) + +func main() { + var addr string + var proxyAddr string + var debug bool + + flag.BoolVar(&debug, "debug", false, "--debug") + flag.StringVar(&addr, "addr", ":8080", "--addr ") + flag.StringVar(&proxyAddr, "proxy-addr", "", "--proxy-addr ") + flag.Parse() + + reverseProxyMap := make(map[string]*httputil.ReverseProxy) + + mux := http.NewServeMux() + + counter := 1 + mux.HandleFunc("/clusters/", func(w http.ResponseWriter, req *http.Request) { + sp := strings.Split(strings.TrimPrefix(req.URL.Path, "/clusters/"), "/") + if len(sp) <= 1 { + http.Error(w, "invalid request", http.StatusForbidden) + return + } + + clusterName := sp[0] + + start := time.Now() + if debug { + log.Printf("[%d] request received /%s\n", counter, strings.Join(sp[1:], "/")) + } + defer func() { + log.Printf("[%d] (took %.2fs) /%s\n", counter, time.Since(start).Seconds(), strings.Join(sp[1:], "/")) + counter = counter + 1 + }() + + if clusterName == "" { + http.Error(w, "kloudlite-cluster is missing", http.StatusForbidden) + return + } + + reverseProxy, ok := reverseProxyMap[clusterName] + if !ok { + reverseProxy = &httputil.ReverseProxy{ + Director: func(req *http.Request) { + req.URL.Scheme = "http" + req.URL.Host = strings.ReplaceAll(proxyAddr, "{{.CLUSTER_NAME}}", clusterName) + req.URL.Path = fmt.Sprintf("/%s", strings.Join(sp[1:], "/")) + }, + } + } + + reverseProxy.ServeHTTP(w, req) + }) + + log.Fatal(http.ListenAndServe(addr, mux)) +} diff --git a/tools/README.md b/tools/README.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/tools/global-vpn-kube-proxy/main.go b/tools/global-vpn-kube-proxy/main.go deleted file mode 100644 index e69de29bb..000000000