Skip to content

Commit

Permalink
service-mesh: add egress implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
3u13r committed Feb 21, 2024
1 parent b0f0fb8 commit 4f821de
Show file tree
Hide file tree
Showing 11 changed files with 432 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ justfile.env
workspace
workspace.cache
.direnv/
go.work.sum
6 changes: 6 additions & 0 deletions go.work
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
go 1.21

use (
.
./service-mesh
)
5 changes: 4 additions & 1 deletion justfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Undeploy, rebuild, deploy.
default target=default_deploy_target: undeploy coordinator initializer openssl port-forwarder (deploy target) set verify (wait-for-workload target)
default target=default_deploy_target: undeploy coordinator initializer openssl port-forwarder service-mesh-proxy (deploy target) set verify (wait-for-workload target)

# Build the coordinator, containerize and push it.
coordinator:
Expand All @@ -13,6 +13,9 @@ openssl:
port-forwarder:
nix run .#containers.push-port-forwarder -- "$container_registry/nunki/port-forwarder"

service-mesh-proxy:
nix run .#containers.push-service-mesh-proxy -- "$container_registry/nunki/service-mesh-proxy"

# Build the initializer, containerize and push it.
initializer:
nix run .#containers.push-initializer -- "$container_registry/nunki/initializer"
Expand Down
4 changes: 3 additions & 1 deletion packages/by-name/nunki/package.nix
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ buildGoModule rec {
fileset = fileset.unions [
(path.append root "go.mod")
(path.append root "go.sum")
(fileset.fileFilter (file: hasSuffix ".go" file.name) root)
(lib.fileset.difference
(lib.fileset.fileFilter (file: lib.hasSuffix ".go" file.name) root)
(path.append root "service-mesh"))
];
};

Expand Down
48 changes: 48 additions & 0 deletions packages/by-name/service-mesh/package.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{ lib
, buildGoModule
}:

buildGoModule rec {
pname = "service-mesh";
version = builtins.readFile ../../../version.txt;

# The source of the main module of this repo. We filter for Go files so that
# changes in the other parts of this repo don't trigger a rebuild.
src =
let
inherit (lib) fileset path hasSuffix;
root = ../../../service-mesh;
in
fileset.toSource {
inherit root;
fileset = fileset.unions [
(path.append root "go.mod")
(path.append root "go.sum")
(lib.fileset.fileFilter (file: lib.hasSuffix ".go" file.name) root)
];
};

proxyVendor = true;
vendorHash = "sha256-uNd8HOd1HXhTvksoVLFsoIof/3VNnBZDLeEraYs5i3s=";

subPackages = [ "." ];

CGO_ENABLED = 0;
ldflags = [
"-s"
"-w"
"-X main.version=v${version}"
];

preCheck = ''
export CGO_ENABLED=1
'';

checkPhase = ''
runHook preCheck
go test -race ./...
runHook postCheck
'';

meta.mainProgram = "service-mesh";
}
10 changes: 10 additions & 0 deletions packages/containers.nix
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ let
tag = "v${nunki.version}";
copyToRoot = [ bash socat ];
};

service-mesh-proxy = dockerTools.buildImage {
name = "service-mesh-proxy";
tag = "v${service-mesh.version}";
copyToRoot = [ envoy ];
config = {
Cmd = [ "${service-mesh}/bin/service-mesh" ];
Env = [ "PATH=/bin" ]; # This is only here for policy generation.
};
};
};
in
containers // (lib.concatMapAttrs (name: container: { "push-${name}" = pushContainer container; }) containers)
5 changes: 4 additions & 1 deletion packages/scripts.nix
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,20 @@ with pkgs;
gunzip < "${containers.initializer}" > "$tmpdir/initializer.tar"
gunzip < "${containers.openssl}" > "$tmpdir/openssl.tar"
gunzip < "${containers.port-forwarder}" > "$tmpdir/port-forwarder.tar"
gunzip < "${containers.service-mesh-proxy}" > "$tmpdir/service-mesh-proxy.tar"
coordHash=$(crane digest --tarball "$tmpdir/coordinator.tar")
initHash=$(crane digest --tarball "$tmpdir/initializer.tar")
opensslHash=$(crane digest --tarball "$tmpdir/openssl.tar")
forwarderHash=$(crane digest --tarball "$tmpdir/port-forwarder.tar")
serviceMeshProxyHash=$(crane digest --tarball "$tmpdir/service-mesh-proxy.tar")
kypatch images "$targetPath" \
--replace "nunki/coordinator:latest" "nunki/coordinator@$coordHash" \
--replace "nunki/initializer:latest" "nunki/initializer@$initHash" \
--replace "nunki/openssl:latest" "nunki/openssl@$opensslHash" \
--replace "nunki/port-forwarder:latest" "nunki/port-forwarder@$forwarderHash"
--replace "nunki/port-forwarder:latest" "nunki/port-forwarder@$forwarderHash" \
--replace "nunki/service-mesh-proxy:latest" "nunki/service-mesh-proxy@$serviceMeshProxyHash"
'';
};

Expand Down
220 changes: 220 additions & 0 deletions service-mesh/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
package main

import (
"fmt"
"net/netip"
"strings"

envoyConfigBootstrapV3 "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v3"
envoyConfigClusterV3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
envoyCoreV3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
endpointV3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
envoyConfigListenerV3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
envoyConfigTCPProxyV3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3"
envoyTLSV3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/anypb"
)

var loopbackCIDR = netip.MustParsePrefix("127.0.0.1/8")

type proxyConfig []configEntry

type configEntry struct {
name string
listenAddr netip.Addr
port uint16
remoteDomain string
}

// ParseProxyConfig parses the proxy configuration from the given string.
// The configuration is expected to be in the following format:
//
// <name>#<listen-address>:<port>#<remote-domain>##<name>#<listen-address>:<port>#<remote-domain>...
//
// Example:
//
// emoji#127.137.0.1:8080#emoji-svc##voting#127.137.0.2:8080#voting-svc
func ParseProxyConfig(data string) (proxyConfig, error) {
entries := strings.Split(data, "##")
var cfg proxyConfig
for _, entry := range entries {
parts := strings.Split(entry, "#")
if len(parts) != 3 {
return nil, fmt.Errorf("invalid entry: %s", entry)
}
listenAddrPort, err := netip.ParseAddrPort(parts[1])
if err != nil {
return nil, fmt.Errorf("invalid listen address: %s", parts[1])
}

if !loopbackCIDR.Contains(listenAddrPort.Addr()) {
return nil, fmt.Errorf("listen address %s is not in local CIDR %s", listenAddrPort.Addr(), loopbackCIDR)
}
remoteDomain := parts[2]
cfg = append(cfg, configEntry{
name: parts[0],
listenAddr: listenAddrPort.Addr(),
port: listenAddrPort.Port(),
remoteDomain: remoteDomain,
})
}
return cfg, nil
}

// ToEnvoyConfig converts the proxy configuration to an Envoy configuration.
// Reference: https://github.com/solo-io/envoy-operator/blob/master/pkg/kube/config.go
func (c proxyConfig) ToEnvoyConfig() ([]byte, error) {
config := &envoyConfigBootstrapV3.Bootstrap{
StaticResources: &envoyConfigBootstrapV3.Bootstrap_StaticResources{},
}
listeners := make([]*envoyConfigListenerV3.Listener, 0, len(c))
clusters := make([]*envoyConfigClusterV3.Cluster, 0, len(c))
for _, entry := range c {
listener, err := listener(entry)
if err != nil {
return nil, err
}
listeners = append(listeners, listener)
cluster, err := cluster(entry)
if err != nil {
return nil, err
}
clusters = append(clusters, cluster)
}
config.StaticResources.Listeners = listeners
config.StaticResources.Clusters = clusters

if err := config.ValidateAll(); err != nil {
return nil, err
}

configBytes, err := protojson.Marshal(config)
if err != nil {
return nil, err
}

return configBytes, nil
}

func listener(entry configEntry) (*envoyConfigListenerV3.Listener, error) {
proxy := &envoyConfigTCPProxyV3.TcpProxy{
StatPrefix: entry.name,
ClusterSpecifier: &envoyConfigTCPProxyV3.TcpProxy_Cluster{
Cluster: entry.name,
},
}

proxyAny, err := anypb.New(proxy)
if err != nil {
return nil, err
}

return &envoyConfigListenerV3.Listener{
Name: entry.name,
Address: &envoyCoreV3.Address{
Address: &envoyCoreV3.Address_SocketAddress{
SocketAddress: &envoyCoreV3.SocketAddress{
Address: entry.listenAddr.String(),
PortSpecifier: &envoyCoreV3.SocketAddress_PortValue{
PortValue: uint32(entry.port),
},
},
},
},
FilterChains: []*envoyConfigListenerV3.FilterChain{
{
Filters: []*envoyConfigListenerV3.Filter{
{
Name: "envoy.filters.network.tcp_proxy",
ConfigType: &envoyConfigListenerV3.Filter_TypedConfig{
TypedConfig: proxyAny,
},
},
},
},
},
}, nil
}

func cluster(entry configEntry) (*envoyConfigClusterV3.Cluster, error) {
socket, err := tlsTransportSocket()
if err != nil {
return nil, err
}

return &envoyConfigClusterV3.Cluster{
Name: entry.name,
ClusterDiscoveryType: &envoyConfigClusterV3.Cluster_Type{
Type: envoyConfigClusterV3.Cluster_LOGICAL_DNS,
},
DnsLookupFamily: envoyConfigClusterV3.Cluster_V4_ONLY,
LoadAssignment: &endpointV3.ClusterLoadAssignment{
ClusterName: entry.name,
Endpoints: []*endpointV3.LocalityLbEndpoints{
{
LbEndpoints: []*endpointV3.LbEndpoint{
{
HostIdentifier: &endpointV3.LbEndpoint_Endpoint{
Endpoint: &endpointV3.Endpoint{
Address: &envoyCoreV3.Address{
Address: &envoyCoreV3.Address_SocketAddress{
SocketAddress: &envoyCoreV3.SocketAddress{
Address: entry.remoteDomain,
PortSpecifier: &envoyCoreV3.SocketAddress_PortValue{
PortValue: uint32(entry.port),
},
},
},
},
},
},
},
},
},
},
},
TransportSocket: socket,
}, nil
}

func tlsTransportSocket() (*envoyCoreV3.TransportSocket, error) {
tls := &envoyTLSV3.UpstreamTlsContext{
CommonTlsContext: &envoyTLSV3.CommonTlsContext{
TlsCertificates: []*envoyTLSV3.TlsCertificate{
{
PrivateKey: &envoyCoreV3.DataSource{
Specifier: &envoyCoreV3.DataSource_Filename{
Filename: "/tls-config/key.pem",
},
},
CertificateChain: &envoyCoreV3.DataSource{
Specifier: &envoyCoreV3.DataSource_Filename{
Filename: "/tls-config/certChain.pem",
},
},
},
},
ValidationContextType: &envoyTLSV3.CommonTlsContext_ValidationContext{
ValidationContext: &envoyTLSV3.CertificateValidationContext{
TrustedCa: &envoyCoreV3.DataSource{
Specifier: &envoyCoreV3.DataSource_Filename{
Filename: "/tls-config/MeshCACert.pem",
},
},
},
},
},
}
tlsAny, err := anypb.New(tls)
if err != nil {
return nil, err
}

return &envoyCoreV3.TransportSocket{
Name: "envoy.transport_sockets.tls",
ConfigType: &envoyCoreV3.TransportSocket_TypedConfig{
TypedConfig: tlsAny,
},
}, nil
}
17 changes: 17 additions & 0 deletions service-mesh/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module github.com/edgelesssys/nunki/service-mesh

go 1.21

require (
github.com/envoyproxy/go-control-plane v0.12.0
google.golang.org/protobuf v1.32.0
)

require (
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
)
Loading

0 comments on commit 4f821de

Please sign in to comment.