mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-07-20 22:08:53 -04:00
Delete Coordinator core and apis
This commit is contained in:
parent
e534c6a338
commit
32f1f5fd3e
93 changed files with 1824 additions and 16487 deletions
|
@ -1,6 +1,7 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -9,29 +10,26 @@ import (
|
|||
"net"
|
||||
"strconv"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/cli/internal/azure"
|
||||
"github.com/edgelesssys/constellation/cli/internal/cloudcmd"
|
||||
"github.com/edgelesssys/constellation/cli/internal/gcp"
|
||||
"github.com/edgelesssys/constellation/cli/internal/proto"
|
||||
"github.com/edgelesssys/constellation/cli/internal/vpn"
|
||||
"github.com/edgelesssys/constellation/coordinator/pubapi/pubproto"
|
||||
coordinatorstate "github.com/edgelesssys/constellation/coordinator/state"
|
||||
"github.com/edgelesssys/constellation/coordinator/initproto"
|
||||
"github.com/edgelesssys/constellation/coordinator/kms"
|
||||
"github.com/edgelesssys/constellation/coordinator/util"
|
||||
"github.com/edgelesssys/constellation/internal/atls"
|
||||
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/internal/cloud/cloudtypes"
|
||||
"github.com/edgelesssys/constellation/internal/config"
|
||||
"github.com/edgelesssys/constellation/internal/constants"
|
||||
"github.com/edgelesssys/constellation/internal/deploy/ssh"
|
||||
"github.com/edgelesssys/constellation/internal/file"
|
||||
"github.com/edgelesssys/constellation/internal/grpc/dialer"
|
||||
"github.com/edgelesssys/constellation/internal/grpc/retry"
|
||||
"github.com/edgelesssys/constellation/internal/state"
|
||||
"github.com/edgelesssys/constellation/internal/statuswaiter"
|
||||
"github.com/kr/text"
|
||||
wgquick "github.com/nmiculinic/wg-quick-go"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// NewInitCmd returns a new cobra.Command for the init command.
|
||||
|
@ -44,10 +42,7 @@ func NewInitCmd() *cobra.Command {
|
|||
Args: cobra.ExactArgs(0),
|
||||
RunE: runInitialize,
|
||||
}
|
||||
cmd.Flags().String("privatekey", "", "path to your private key")
|
||||
cmd.Flags().String("master-secret", "", "path to base64-encoded master secret")
|
||||
cmd.Flags().Bool("wg-autoconfig", false, "enable automatic configuration of WireGuard interface")
|
||||
must(cmd.Flags().MarkHidden("wg-autoconfig"))
|
||||
cmd.Flags().Bool("autoscale", false, "enable Kubernetes cluster-autoscaler")
|
||||
return cmd
|
||||
}
|
||||
|
@ -55,19 +50,15 @@ func NewInitCmd() *cobra.Command {
|
|||
// runInitialize runs the initialize command.
|
||||
func runInitialize(cmd *cobra.Command, args []string) error {
|
||||
fileHandler := file.NewHandler(afero.NewOsFs())
|
||||
vpnHandler := vpn.NewConfigHandler()
|
||||
serviceAccountCreator := cloudcmd.NewServiceAccountCreator()
|
||||
waiter := statuswaiter.New()
|
||||
protoClient := &proto.Client{}
|
||||
defer protoClient.Close()
|
||||
|
||||
return initialize(cmd, protoClient, serviceAccountCreator, fileHandler, waiter, vpnHandler)
|
||||
dialer := dialer.New(nil, nil, &net.Dialer{})
|
||||
return initialize(cmd, dialer, serviceAccountCreator, fileHandler)
|
||||
}
|
||||
|
||||
// initialize initializes a Constellation. Coordinator instances are activated as contole-plane nodes and will
|
||||
// themself activate the other peers as workers.
|
||||
func initialize(cmd *cobra.Command, protCl protoClient, serviceAccCreator serviceAccountCreator,
|
||||
fileHandler file.Handler, waiter statusWaiter, vpnHandler vpnHandler,
|
||||
func initialize(cmd *cobra.Command, dialer grpcDialer, serviceAccCreator serviceAccountCreator,
|
||||
fileHandler file.Handler,
|
||||
) error {
|
||||
flags, err := evalFlagArgs(cmd, fileHandler)
|
||||
if err != nil {
|
||||
|
@ -117,153 +108,79 @@ func initialize(cmd *cobra.Command, protCl protoClient, serviceAccCreator servic
|
|||
return err
|
||||
}
|
||||
|
||||
endpoints := ipsToEndpoints(append(coordinators.PublicIPs(), nodes.PublicIPs()...), strconv.Itoa(constants.CoordinatorPort))
|
||||
|
||||
cmd.Println("Waiting for cloud provider resource creation and boot ...")
|
||||
if err := waiter.InitializeValidators(validators.V()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := waiter.WaitForAll(cmd.Context(), endpoints, coordinatorstate.AcceptingInit); err != nil {
|
||||
return fmt.Errorf("waiting for all peers status: %w", err)
|
||||
}
|
||||
|
||||
var autoscalingNodeGroups []string
|
||||
if flags.autoscale {
|
||||
autoscalingNodeGroups = append(autoscalingNodeGroups, nodes.GroupID)
|
||||
}
|
||||
|
||||
input := activationInput{
|
||||
coordinatorPubIP: coordinators.PublicIPs()[0],
|
||||
pubKey: flags.userPubKey,
|
||||
masterSecret: flags.masterSecret,
|
||||
nodePrivIPs: nodes.PrivateIPs(),
|
||||
coordinatorPrivIPs: coordinators.PrivateIPs()[1:],
|
||||
autoscalingNodeGroups: autoscalingNodeGroups,
|
||||
cloudServiceAccountURI: serviceAccount,
|
||||
sshUserKeys: ssh.ToProtoSlice(sshUsers),
|
||||
req := &initproto.InitRequest{
|
||||
AutoscalingNodeGroups: autoscalingNodeGroups,
|
||||
MasterSecret: flags.masterSecret,
|
||||
KmsUri: kms.ClusterKMSURI,
|
||||
StorageUri: kms.NoStoreURI,
|
||||
KeyEncryptionKeyId: "",
|
||||
UseExistingKek: false,
|
||||
CloudServiceAccountUri: serviceAccount,
|
||||
KubernetesVersion: "1.23.6",
|
||||
SshUserKeys: ssh.ToProtoSlice(sshUsers),
|
||||
}
|
||||
result, err := activate(cmd, protCl, input, validators.V())
|
||||
resp, err := initCall(cmd.Context(), dialer, coordinators.PublicIPs()[0], req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = result.writeOutput(cmd.OutOrStdout(), fileHandler)
|
||||
if err != nil {
|
||||
if err := writeOutput(resp, cmd.OutOrStdout(), fileHandler); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vpnConfig, err := vpnHandler.Create(result.coordinatorPubKey, result.coordinatorPubIP, string(flags.userPrivKey), result.clientVpnIP, constants.WireguardAdminMTU)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeWGQuickFile(fileHandler, vpnHandler, vpnConfig); err != nil {
|
||||
return fmt.Errorf("writing wg-quick file: %w", err)
|
||||
}
|
||||
|
||||
if flags.autoconfigureWG {
|
||||
if err := vpnHandler.Apply(vpnConfig); err != nil {
|
||||
return fmt.Errorf("configuring WireGuard: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func activate(cmd *cobra.Command, client protoClient, input activationInput,
|
||||
validators []atls.Validator,
|
||||
) (activationResult, error) {
|
||||
err := client.Connect(net.JoinHostPort(input.coordinatorPubIP, strconv.Itoa(constants.CoordinatorPort)), validators)
|
||||
if err != nil {
|
||||
return activationResult{}, err
|
||||
func initCall(ctx context.Context, dialer grpcDialer, ip string, req *initproto.InitRequest) (*initproto.InitResponse, error) {
|
||||
doer := &initDoer{
|
||||
dialer: dialer,
|
||||
endpoint: net.JoinHostPort(ip, strconv.Itoa(constants.CoordinatorPort)),
|
||||
req: req,
|
||||
}
|
||||
|
||||
respCl, err := client.Activate(cmd.Context(), input.pubKey, input.masterSecret, input.nodePrivIPs, input.coordinatorPrivIPs, input.autoscalingNodeGroups, input.cloudServiceAccountURI, input.sshUserKeys)
|
||||
if err != nil {
|
||||
return activationResult{}, err
|
||||
retryer := retry.NewIntervalRetryer(doer, 30*time.Second)
|
||||
if err := retryer.Do(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
indentOut := text.NewIndentWriter(cmd.OutOrStdout(), []byte{'\t'})
|
||||
cmd.Println("Activating the cluster ...")
|
||||
if err := respCl.WriteLogStream(indentOut); err != nil {
|
||||
return activationResult{}, err
|
||||
}
|
||||
|
||||
clientVpnIp, err := respCl.GetClientVpnIp()
|
||||
if err != nil {
|
||||
return activationResult{}, err
|
||||
}
|
||||
coordinatorPubKey, err := respCl.GetCoordinatorVpnKey()
|
||||
if err != nil {
|
||||
return activationResult{}, err
|
||||
}
|
||||
kubeconfig, err := respCl.GetKubeconfig()
|
||||
if err != nil {
|
||||
return activationResult{}, err
|
||||
}
|
||||
ownerID, err := respCl.GetOwnerID()
|
||||
if err != nil {
|
||||
return activationResult{}, err
|
||||
}
|
||||
clusterID, err := respCl.GetClusterID()
|
||||
if err != nil {
|
||||
return activationResult{}, err
|
||||
}
|
||||
|
||||
return activationResult{
|
||||
clientVpnIP: clientVpnIp,
|
||||
coordinatorPubKey: coordinatorPubKey,
|
||||
coordinatorPubIP: input.coordinatorPubIP,
|
||||
kubeconfig: kubeconfig,
|
||||
ownerID: ownerID,
|
||||
clusterID: clusterID,
|
||||
}, nil
|
||||
return doer.resp, nil
|
||||
}
|
||||
|
||||
type activationInput struct {
|
||||
coordinatorPubIP string
|
||||
pubKey []byte
|
||||
masterSecret []byte
|
||||
nodePrivIPs []string
|
||||
coordinatorPrivIPs []string
|
||||
autoscalingNodeGroups []string
|
||||
cloudServiceAccountURI string
|
||||
sshUserKeys []*pubproto.SSHUserKey
|
||||
type initDoer struct {
|
||||
dialer grpcDialer
|
||||
endpoint string
|
||||
req *initproto.InitRequest
|
||||
resp *initproto.InitResponse
|
||||
}
|
||||
|
||||
type activationResult struct {
|
||||
clientVpnIP string
|
||||
coordinatorPubKey string
|
||||
coordinatorPubIP string
|
||||
kubeconfig string
|
||||
ownerID string
|
||||
clusterID string
|
||||
}
|
||||
|
||||
// writeWGQuickFile writes the wg-quick file to the default path.
|
||||
func writeWGQuickFile(fileHandler file.Handler, vpnHandler vpnHandler, vpnConfig *wgquick.Config) error {
|
||||
data, err := vpnHandler.Marshal(vpnConfig)
|
||||
func (d *initDoer) Do(ctx context.Context) error {
|
||||
conn, err := d.dialer.Dial(ctx, d.endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dialing init server: %w", err)
|
||||
}
|
||||
protoClient := initproto.NewAPIClient(conn)
|
||||
resp, err := protoClient.Init(ctx, d.req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshalling VPN config: %w", err)
|
||||
}
|
||||
return fileHandler.Write(constants.WGQuickConfigFilename, data, file.OptNone)
|
||||
d.resp = resp
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r activationResult) writeOutput(wr io.Writer, fileHandler file.Handler) error {
|
||||
func writeOutput(resp *initproto.InitResponse, wr io.Writer, fileHandler file.Handler) error {
|
||||
fmt.Fprint(wr, "Your Constellation cluster was successfully initialized.\n\n")
|
||||
|
||||
tw := tabwriter.NewWriter(wr, 0, 0, 2, ' ', 0)
|
||||
writeRow(tw, "Your WireGuard IP", r.clientVpnIP)
|
||||
writeRow(tw, "Control plane's public IP", r.coordinatorPubIP)
|
||||
writeRow(tw, "Control plane's public key", r.coordinatorPubKey)
|
||||
writeRow(tw, "Constellation cluster's owner identifier", r.ownerID)
|
||||
writeRow(tw, "Constellation cluster's unique identifier", r.clusterID)
|
||||
writeRow(tw, "WireGuard configuration file", constants.WGQuickConfigFilename)
|
||||
writeRow(tw, "Constellation cluster's owner identifier", string(resp.OwnerId))
|
||||
writeRow(tw, "Constellation cluster's unique identifier", string(resp.ClusterId))
|
||||
writeRow(tw, "Kubernetes configuration", constants.AdminConfFilename)
|
||||
tw.Flush()
|
||||
fmt.Fprintln(wr)
|
||||
|
||||
if err := fileHandler.Write(constants.AdminConfFilename, []byte(r.kubeconfig), file.OptNone); err != nil {
|
||||
if err := fileHandler.Write(constants.AdminConfFilename, resp.Kubeconfig, file.OptNone); err != nil {
|
||||
return fmt.Errorf("write kubeconfig: %w", err)
|
||||
}
|
||||
|
||||
|
@ -273,7 +190,6 @@ func (r activationResult) writeOutput(wr io.Writer, fileHandler file.Handler) er
|
|||
}
|
||||
|
||||
fmt.Fprintln(wr, "You can now connect to your cluster by executing:")
|
||||
fmt.Fprintf(wr, "\twg-quick up ./%s\n", constants.WGQuickConfigFilename)
|
||||
fmt.Fprintf(wr, "\texport KUBECONFIG=\"$PWD/%s\"\n", constants.AdminConfFilename)
|
||||
return nil
|
||||
}
|
||||
|
@ -285,18 +201,6 @@ func writeRow(wr io.Writer, col1 string, col2 string) {
|
|||
// evalFlagArgs gets the flag values and does preprocessing of these values like
|
||||
// reading the content from file path flags and deriving other values from flag combinations.
|
||||
func evalFlagArgs(cmd *cobra.Command, fileHandler file.Handler) (initFlags, error) {
|
||||
userPrivKeyPath, err := cmd.Flags().GetString("privatekey")
|
||||
if err != nil {
|
||||
return initFlags{}, fmt.Errorf("parsing privatekey path argument: %w", err)
|
||||
}
|
||||
userPrivKey, userPubKey, err := readOrGenerateVPNKey(fileHandler, userPrivKeyPath)
|
||||
if err != nil {
|
||||
return initFlags{}, err
|
||||
}
|
||||
autoconfigureWG, err := cmd.Flags().GetBool("wg-autoconfig")
|
||||
if err != nil {
|
||||
return initFlags{}, fmt.Errorf("parsing wg-autoconfig argument: %w", err)
|
||||
}
|
||||
masterSecretPath, err := cmd.Flags().GetString("master-secret")
|
||||
if err != nil {
|
||||
return initFlags{}, fmt.Errorf("parsing master-secret path argument: %w", err)
|
||||
|
@ -315,58 +219,17 @@ func evalFlagArgs(cmd *cobra.Command, fileHandler file.Handler) (initFlags, erro
|
|||
}
|
||||
|
||||
return initFlags{
|
||||
configPath: configPath,
|
||||
userPrivKey: userPrivKey,
|
||||
userPubKey: userPubKey,
|
||||
autoconfigureWG: autoconfigureWG,
|
||||
autoscale: autoscale,
|
||||
masterSecret: masterSecret,
|
||||
configPath: configPath,
|
||||
autoscale: autoscale,
|
||||
masterSecret: masterSecret,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// initFlags are the resulting values of flag preprocessing.
|
||||
type initFlags struct {
|
||||
configPath string
|
||||
userPrivKey []byte
|
||||
userPubKey []byte
|
||||
masterSecret []byte
|
||||
autoconfigureWG bool
|
||||
autoscale bool
|
||||
}
|
||||
|
||||
func readOrGenerateVPNKey(fileHandler file.Handler, privKeyPath string) (privKey, pubKey []byte, err error) {
|
||||
var privKeyParsed wgtypes.Key
|
||||
if privKeyPath == "" {
|
||||
privKeyParsed, err = wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("generating WireGuard private key: %w", err)
|
||||
}
|
||||
privKey = []byte(privKeyParsed.String())
|
||||
} else {
|
||||
privKey, err = fileHandler.Read(privKeyPath)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("reading the VPN private key: %w", err)
|
||||
}
|
||||
privKeyParsed, err = wgtypes.ParseKey(string(privKey))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("parsing the WireGuard private key: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
pubKey = []byte(privKeyParsed.PublicKey().String())
|
||||
|
||||
return privKey, pubKey, nil
|
||||
}
|
||||
|
||||
func ipsToEndpoints(ips []string, port string) []string {
|
||||
var endpoints []string
|
||||
for _, ip := range ips {
|
||||
if ip == "" {
|
||||
continue
|
||||
}
|
||||
endpoints = append(endpoints, net.JoinHostPort(ip, port))
|
||||
}
|
||||
return endpoints
|
||||
configPath string
|
||||
masterSecret []byte
|
||||
autoscale bool
|
||||
}
|
||||
|
||||
// readOrGenerateMasterSecret reads a base64 encoded master secret from file or generates a new 32 byte secret.
|
||||
|
@ -491,3 +354,7 @@ func initCompletion(cmd *cobra.Command, args []string, toComplete string) ([]str
|
|||
}
|
||||
return []string{}, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
|
||||
type grpcDialer interface {
|
||||
Dial(ctx context.Context, target string) (*grpc.ClientConn, error)
|
||||
}
|
||||
|
|
|
@ -5,21 +5,25 @@ import (
|
|||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/coordinator/initproto"
|
||||
"github.com/edgelesssys/constellation/internal/cloud/cloudtypes"
|
||||
"github.com/edgelesssys/constellation/internal/constants"
|
||||
"github.com/edgelesssys/constellation/internal/file"
|
||||
"github.com/edgelesssys/constellation/internal/grpc/atlscredentials"
|
||||
"github.com/edgelesssys/constellation/internal/grpc/dialer"
|
||||
"github.com/edgelesssys/constellation/internal/grpc/testdialer"
|
||||
"github.com/edgelesssys/constellation/internal/state"
|
||||
wgquick "github.com/nmiculinic/wg-quick-go"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func TestInitArgumentValidation(t *testing.T) {
|
||||
|
@ -32,7 +36,6 @@ func TestInitArgumentValidation(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInitialize(t *testing.T) {
|
||||
testKey := base64.StdEncoding.EncodeToString([]byte("32bytesWireGuardKeyForTheTesting"))
|
||||
testGcpState := state.ConstellationState{
|
||||
CloudProvider: "GCP",
|
||||
GCPNodes: cloudtypes.Instances{
|
||||
|
@ -64,221 +67,73 @@ func TestInitialize(t *testing.T) {
|
|||
"id-c": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"},
|
||||
},
|
||||
}
|
||||
testActivationResps := []fakeActivationRespMessage{
|
||||
{log: "testlog1"},
|
||||
{log: "testlog2"},
|
||||
{
|
||||
kubeconfig: "kubeconfig",
|
||||
clientVpnIp: "192.0.2.2",
|
||||
coordinatorVpnKey: testKey,
|
||||
ownerID: "ownerID",
|
||||
clusterID: "clusterID",
|
||||
},
|
||||
{log: "testlog3"},
|
||||
testInitResp := &initproto.InitResponse{
|
||||
Kubeconfig: []byte("kubeconfig"),
|
||||
OwnerId: []byte("ownerID"),
|
||||
ClusterId: []byte("clusterID"),
|
||||
}
|
||||
someErr := errors.New("failed")
|
||||
// someErr := errors.New("failed")
|
||||
|
||||
testCases := map[string]struct {
|
||||
existingState state.ConstellationState
|
||||
client protoClient
|
||||
serviceAccountCreator stubServiceAccountCreator
|
||||
waiter statusWaiter
|
||||
privKey string
|
||||
vpnHandler vpnHandler
|
||||
initVPN bool
|
||||
initServerAPI *stubInitServer
|
||||
setAutoscaleFlag bool
|
||||
wantErr bool
|
||||
}{
|
||||
"initialize some gcp instances": {
|
||||
existingState: testGcpState,
|
||||
client: &fakeProtoClient{
|
||||
respClient: &fakeActivationRespClient{responses: testActivationResps},
|
||||
},
|
||||
waiter: &stubStatusWaiter{},
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
privKey: testKey,
|
||||
initServerAPI: &stubInitServer{initResp: testInitResp},
|
||||
},
|
||||
"initialize some azure instances": {
|
||||
existingState: testAzureState,
|
||||
client: &fakeProtoClient{
|
||||
respClient: &fakeActivationRespClient{responses: testActivationResps},
|
||||
},
|
||||
waiter: &stubStatusWaiter{},
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
privKey: testKey,
|
||||
initServerAPI: &stubInitServer{initResp: testInitResp},
|
||||
},
|
||||
"initialize some qemu instances": {
|
||||
existingState: testQemuState,
|
||||
client: &fakeProtoClient{
|
||||
respClient: &fakeActivationRespClient{responses: testActivationResps},
|
||||
},
|
||||
waiter: &stubStatusWaiter{},
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
privKey: testKey,
|
||||
initServerAPI: &stubInitServer{initResp: testInitResp},
|
||||
},
|
||||
"initialize vpn": {
|
||||
existingState: testAzureState,
|
||||
client: &fakeProtoClient{
|
||||
respClient: &fakeActivationRespClient{responses: testActivationResps},
|
||||
},
|
||||
waiter: &stubStatusWaiter{},
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
initVPN: true,
|
||||
privKey: testKey,
|
||||
"initialize gcp with autoscaling": {
|
||||
existingState: testGcpState,
|
||||
initServerAPI: &stubInitServer{initResp: testInitResp},
|
||||
setAutoscaleFlag: true,
|
||||
},
|
||||
"invalid initialize vpn": {
|
||||
existingState: testAzureState,
|
||||
client: &fakeProtoClient{
|
||||
respClient: &fakeActivationRespClient{responses: testActivationResps},
|
||||
},
|
||||
waiter: &stubStatusWaiter{},
|
||||
vpnHandler: &stubVPNHandler{applyErr: someErr},
|
||||
initVPN: true,
|
||||
privKey: testKey,
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid create vpn config": {
|
||||
existingState: testAzureState,
|
||||
client: &fakeProtoClient{
|
||||
respClient: &fakeActivationRespClient{responses: testActivationResps},
|
||||
},
|
||||
waiter: &stubStatusWaiter{},
|
||||
vpnHandler: &stubVPNHandler{createErr: someErr},
|
||||
initVPN: true,
|
||||
privKey: testKey,
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid write vpn config": {
|
||||
existingState: testAzureState,
|
||||
client: &fakeProtoClient{
|
||||
respClient: &fakeActivationRespClient{responses: testActivationResps},
|
||||
},
|
||||
waiter: &stubStatusWaiter{},
|
||||
vpnHandler: &stubVPNHandler{marshalErr: someErr},
|
||||
initVPN: true,
|
||||
privKey: testKey,
|
||||
wantErr: true,
|
||||
},
|
||||
"no state exists": {
|
||||
existingState: state.ConstellationState{},
|
||||
client: &stubProtoClient{},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: testKey,
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
wantErr: true,
|
||||
},
|
||||
"no instances to pick one": {
|
||||
existingState: state.ConstellationState{GCPNodes: cloudtypes.Instances{}},
|
||||
client: &stubProtoClient{},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: testKey,
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
wantErr: true,
|
||||
},
|
||||
"public key to short": {
|
||||
existingState: testGcpState,
|
||||
client: &stubProtoClient{},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: base64.StdEncoding.EncodeToString([]byte("tooShortKey")),
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
wantErr: true,
|
||||
},
|
||||
"public key to long": {
|
||||
existingState: testGcpState,
|
||||
client: &stubProtoClient{},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: base64.StdEncoding.EncodeToString([]byte("thisWireguardKeyIsToLongAndHasTooManyBytes")),
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
wantErr: true,
|
||||
},
|
||||
"public key not base64": {
|
||||
existingState: testGcpState,
|
||||
client: &stubProtoClient{},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: "this is not base64 encoded",
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
wantErr: true,
|
||||
},
|
||||
"fail Connect": {
|
||||
existingState: testGcpState,
|
||||
client: &stubProtoClient{connectErr: someErr},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: testKey,
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
wantErr: true,
|
||||
},
|
||||
"fail Activate": {
|
||||
existingState: testGcpState,
|
||||
client: &stubProtoClient{activateErr: someErr},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: testKey,
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
wantErr: true,
|
||||
},
|
||||
"fail respClient WriteLogStream": {
|
||||
existingState: testGcpState,
|
||||
client: &stubProtoClient{respClient: &stubActivationRespClient{writeLogStreamErr: someErr}},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: testKey,
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
wantErr: true,
|
||||
},
|
||||
"fail respClient getKubeconfig": {
|
||||
existingState: testGcpState,
|
||||
client: &stubProtoClient{respClient: &stubActivationRespClient{getKubeconfigErr: someErr}},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: testKey,
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
wantErr: true,
|
||||
},
|
||||
"fail respClient getCoordinatorVpnKey": {
|
||||
existingState: testGcpState,
|
||||
client: &stubProtoClient{respClient: &stubActivationRespClient{getCoordinatorVpnKeyErr: someErr}},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: testKey,
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
wantErr: true,
|
||||
},
|
||||
"fail respClient getClientVpnIp": {
|
||||
existingState: testGcpState,
|
||||
client: &stubProtoClient{respClient: &stubActivationRespClient{getClientVpnIpErr: someErr}},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: testKey,
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
wantErr: true,
|
||||
},
|
||||
"fail respClient getOwnerID": {
|
||||
existingState: testGcpState,
|
||||
client: &stubProtoClient{respClient: &stubActivationRespClient{getOwnerIDErr: someErr}},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: testKey,
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
wantErr: true,
|
||||
},
|
||||
"fail respClient getClusterID": {
|
||||
existingState: testGcpState,
|
||||
client: &stubProtoClient{respClient: &stubActivationRespClient{getClusterIDErr: someErr}},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: testKey,
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
wantErr: true,
|
||||
},
|
||||
"fail to wait for required status": {
|
||||
existingState: testGcpState,
|
||||
client: &stubProtoClient{},
|
||||
waiter: &stubStatusWaiter{waitForAllErr: someErr},
|
||||
privKey: testKey,
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
wantErr: true,
|
||||
},
|
||||
"fail to create service account": {
|
||||
existingState: testGcpState,
|
||||
client: &stubProtoClient{},
|
||||
serviceAccountCreator: stubServiceAccountCreator{createErr: someErr},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: testKey,
|
||||
vpnHandler: &stubVPNHandler{},
|
||||
wantErr: true,
|
||||
"initialize azure with autoscaling": {
|
||||
existingState: testAzureState,
|
||||
initServerAPI: &stubInitServer{initResp: testInitResp},
|
||||
setAutoscaleFlag: true,
|
||||
},
|
||||
// "no state exists": {
|
||||
// existingState: state.ConstellationState{},
|
||||
// initServerAPI: &stubInitServer{},
|
||||
// wantErr: true,
|
||||
// },
|
||||
// "no instances to pick one": {
|
||||
// existingState: state.ConstellationState{GCPNodes: cloudtypes.Instances{}},
|
||||
// initServerAPI: &stubInitServer{},
|
||||
// wantErr: true,
|
||||
// },
|
||||
// "fail Connect": {
|
||||
// existingState: testGcpState,
|
||||
// initServerAPI: &stubInitServer{},
|
||||
// wantErr: true,
|
||||
// },
|
||||
// "fail Activate": {
|
||||
// existingState: testGcpState,
|
||||
// initServerAPI: &stubInitServer{},
|
||||
// wantErr: true,
|
||||
// },
|
||||
// "fail to wait for required status": {
|
||||
// existingState: testGcpState,
|
||||
// initServerAPI: &stubInitServer{},
|
||||
// wantErr: true,
|
||||
// },
|
||||
// "fail to create service account": {
|
||||
// existingState: testGcpState,
|
||||
// initServerAPI: &stubInitServer{},
|
||||
// serviceAccountCreator: stubServiceAccountCreator{createErr: someErr},
|
||||
// wantErr: true,
|
||||
// },
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
|
@ -286,6 +141,16 @@ func TestInitialize(t *testing.T) {
|
|||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
netDialer := testdialer.NewBufconnDialer()
|
||||
dialer := dialer.New(nil, nil, netDialer)
|
||||
serverCreds := atlscredentials.New(nil, nil)
|
||||
initServer := grpc.NewServer(grpc.Creds(serverCreds))
|
||||
initproto.RegisterAPIServer(initServer, tc.initServerAPI)
|
||||
port := strconv.Itoa(constants.CoordinatorPort)
|
||||
listener := netDialer.GetListener(net.JoinHostPort("192.0.2.1", port))
|
||||
go initServer.Serve(listener)
|
||||
defer initServer.GracefulStop()
|
||||
|
||||
cmd := NewInitCmd()
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
|
@ -295,29 +160,27 @@ func TestInitialize(t *testing.T) {
|
|||
fs := afero.NewMemMapFs()
|
||||
fileHandler := file.NewHandler(fs)
|
||||
require.NoError(fileHandler.WriteJSON(constants.StateFilename, tc.existingState, file.OptNone))
|
||||
|
||||
// Write key file to filesystem and set path in flag.
|
||||
require.NoError(afero.Afero{Fs: fs}.WriteFile("privK", []byte(tc.privKey), 0o600))
|
||||
require.NoError(cmd.Flags().Set("privatekey", "privK"))
|
||||
if tc.initVPN {
|
||||
require.NoError(cmd.Flags().Set("wg-autoconfig", "true"))
|
||||
}
|
||||
require.NoError(cmd.Flags().Set("autoscale", strconv.FormatBool(tc.setAutoscaleFlag)))
|
||||
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
|
||||
defer cancel()
|
||||
cmd.SetContext(ctx)
|
||||
|
||||
err := initialize(cmd, tc.client, &tc.serviceAccountCreator, fileHandler, tc.waiter, tc.vpnHandler)
|
||||
err := initialize(cmd, dialer, &tc.serviceAccountCreator, fileHandler)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Contains(out.String(), "192.0.2.2")
|
||||
assert.Contains(out.String(), "ownerID")
|
||||
assert.Contains(out.String(), "clusterID")
|
||||
if tc.setAutoscaleFlag {
|
||||
assert.Len(tc.initServerAPI.activateAutoscalingNodeGroups, 1)
|
||||
} else {
|
||||
require.NoError(err)
|
||||
assert.Equal(tc.initVPN, tc.vpnHandler.(*stubVPNHandler).configured)
|
||||
assert.Contains(out.String(), "192.0.2.2")
|
||||
assert.Contains(out.String(), "ownerID")
|
||||
assert.Contains(out.String(), "clusterID")
|
||||
assert.Len(tc.initServerAPI.activateAutoscalingNodeGroups, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -326,11 +189,10 @@ func TestInitialize(t *testing.T) {
|
|||
func TestWriteOutput(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
result := activationResult{
|
||||
clientVpnIP: "foo-qq",
|
||||
coordinatorPubKey: "bar-qq",
|
||||
coordinatorPubIP: "baz-qq",
|
||||
kubeconfig: "foo-bar-baz-qq",
|
||||
resp := &initproto.InitResponse{
|
||||
OwnerId: []byte("ownerID"),
|
||||
ClusterId: []byte("clusterID"),
|
||||
Kubeconfig: []byte("kubeconfig"),
|
||||
}
|
||||
|
||||
expectedIdFile := clusterIDsFile{
|
||||
|
@ -343,13 +205,12 @@ func TestWriteOutput(t *testing.T) {
|
|||
testFs := afero.NewMemMapFs()
|
||||
fileHandler := file.NewHandler(testFs)
|
||||
|
||||
err := result.writeOutput(&out, fileHandler)
|
||||
err := writeOutput(resp, &out, fileHandler)
|
||||
assert.NoError(err)
|
||||
assert.Contains(out.String(), result.clientVpnIP)
|
||||
assert.Contains(out.String(), result.coordinatorPubIP)
|
||||
assert.Contains(out.String(), result.coordinatorPubKey)
|
||||
assert.Contains(out.String(), result.clusterID)
|
||||
assert.Contains(out.String(), result.ownerID)
|
||||
assert.Contains(out.String(), resp.OwnerId)
|
||||
assert.Contains(out.String(), resp.ClusterId)
|
||||
assert.Contains(out.String(), constants.AdminConfFilename)
|
||||
assert.Equal(resp.Kubeconfig, string(adminConf))
|
||||
|
||||
afs := afero.Afero{Fs: testFs}
|
||||
adminConf, err := afs.ReadFile(constants.AdminConfFilename)
|
||||
|
@ -364,15 +225,6 @@ func TestWriteOutput(t *testing.T) {
|
|||
assert.Equal(expectedIdFile, testIdFile)
|
||||
}
|
||||
|
||||
func TestIpsToEndpoints(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ips := []string{"192.0.2.1", "192.0.2.2", "", "192.0.2.3"}
|
||||
port := "8080"
|
||||
endpoints := ipsToEndpoints(ips, port)
|
||||
assert.Equal([]string{"192.0.2.1:8080", "192.0.2.2:8080", "192.0.2.3:8080"}, endpoints)
|
||||
}
|
||||
|
||||
func TestInitCompletion(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
args []string
|
||||
|
@ -412,27 +264,7 @@ func TestInitCompletion(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestReadOrGenerateVPNKey(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
testKey := []byte(base64.StdEncoding.EncodeToString([]byte("32bytesWireGuardKeyForTheTesting")))
|
||||
fileHandler := file.NewHandler(afero.NewMemMapFs())
|
||||
require.NoError(fileHandler.Write("testKey", testKey, file.OptNone))
|
||||
|
||||
privK, pubK, err := readOrGenerateVPNKey(fileHandler, "testKey")
|
||||
assert.NoError(err)
|
||||
assert.Equal(testKey, privK)
|
||||
assert.NotEmpty(pubK)
|
||||
|
||||
// no path provided
|
||||
privK, pubK, err = readOrGenerateVPNKey(fileHandler, "")
|
||||
assert.NoError(err)
|
||||
assert.NotEmpty(privK)
|
||||
assert.NotEmpty(pubK)
|
||||
}
|
||||
|
||||
func TestReadOrGenerateMasterSecret(t *testing.T) {
|
||||
func TestReadOrGeneratedMasterSecret(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
filename string
|
||||
filecontent string
|
||||
|
@ -523,157 +355,16 @@ func TestReadOrGenerateMasterSecret(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAutoscaleFlag(t *testing.T) {
|
||||
testKey := base64.StdEncoding.EncodeToString([]byte("32bytesWireGuardKeyForTheTesting"))
|
||||
testGcpState := state.ConstellationState{
|
||||
CloudProvider: "gcp",
|
||||
GCPNodes: cloudtypes.Instances{
|
||||
"id-0": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"},
|
||||
"id-1": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"},
|
||||
},
|
||||
GCPCoordinators: cloudtypes.Instances{
|
||||
"id-c": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"},
|
||||
},
|
||||
}
|
||||
testAzureState := state.ConstellationState{
|
||||
CloudProvider: "azure",
|
||||
AzureNodes: cloudtypes.Instances{
|
||||
"id-0": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"},
|
||||
"id-1": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"},
|
||||
},
|
||||
AzureCoordinators: cloudtypes.Instances{
|
||||
"id-c": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"},
|
||||
},
|
||||
AzureResourceGroup: "test",
|
||||
}
|
||||
testActivationResps := []fakeActivationRespMessage{
|
||||
{log: "testlog1"},
|
||||
{log: "testlog2"},
|
||||
{
|
||||
kubeconfig: "kubeconfig",
|
||||
clientVpnIp: "192.0.2.2",
|
||||
coordinatorVpnKey: testKey,
|
||||
ownerID: "ownerID",
|
||||
clusterID: "clusterID",
|
||||
},
|
||||
{log: "testlog3"},
|
||||
}
|
||||
type stubInitServer struct {
|
||||
initResp *initproto.InitResponse
|
||||
initErr error
|
||||
|
||||
testCases := map[string]struct {
|
||||
autoscaleFlag bool
|
||||
existingState state.ConstellationState
|
||||
client *stubProtoClient
|
||||
serviceAccountCreator stubServiceAccountCreator
|
||||
waiter statusWaiter
|
||||
privKey string
|
||||
}{
|
||||
"initialize some gcp instances without autoscale flag": {
|
||||
autoscaleFlag: false,
|
||||
existingState: testGcpState,
|
||||
client: &stubProtoClient{
|
||||
respClient: &fakeActivationRespClient{responses: testActivationResps},
|
||||
},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: testKey,
|
||||
},
|
||||
"initialize some azure instances without autoscale flag": {
|
||||
autoscaleFlag: false,
|
||||
existingState: testAzureState,
|
||||
client: &stubProtoClient{
|
||||
respClient: &fakeActivationRespClient{responses: testActivationResps},
|
||||
},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: testKey,
|
||||
},
|
||||
"initialize some gcp instances with autoscale flag": {
|
||||
autoscaleFlag: true,
|
||||
existingState: testGcpState,
|
||||
client: &stubProtoClient{
|
||||
respClient: &fakeActivationRespClient{responses: testActivationResps},
|
||||
},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: testKey,
|
||||
},
|
||||
"initialize some azure instances with autoscale flag": {
|
||||
autoscaleFlag: true,
|
||||
existingState: testAzureState,
|
||||
client: &stubProtoClient{
|
||||
respClient: &fakeActivationRespClient{responses: testActivationResps},
|
||||
},
|
||||
waiter: &stubStatusWaiter{},
|
||||
privKey: testKey,
|
||||
},
|
||||
}
|
||||
activateAutoscalingNodeGroups []string
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
cmd := NewInitCmd()
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
var errOut bytes.Buffer
|
||||
cmd.SetErr(&errOut)
|
||||
cmd.Flags().String("config", "", "") // register persisten flag manually
|
||||
fs := afero.NewMemMapFs()
|
||||
fileHandler := file.NewHandler(fs)
|
||||
vpnHandler := stubVPNHandler{}
|
||||
require.NoError(fileHandler.WriteJSON(constants.StateFilename, tc.existingState, file.OptNone))
|
||||
|
||||
// Write key file to filesystem and set path in flag.
|
||||
require.NoError(afero.Afero{Fs: fs}.WriteFile("privK", []byte(tc.privKey), 0o600))
|
||||
require.NoError(cmd.Flags().Set("privatekey", "privK"))
|
||||
|
||||
require.NoError(cmd.Flags().Set("autoscale", strconv.FormatBool(tc.autoscaleFlag)))
|
||||
|
||||
require.NoError(initialize(cmd, tc.client, &tc.serviceAccountCreator, fileHandler, tc.waiter, &vpnHandler))
|
||||
if tc.autoscaleFlag {
|
||||
assert.Len(tc.client.activateAutoscalingNodeGroups, 1)
|
||||
} else {
|
||||
assert.Len(tc.client.activateAutoscalingNodeGroups, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
initproto.UnimplementedAPIServer
|
||||
}
|
||||
|
||||
func TestWriteWGQuickFile(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
fileHandler file.Handler
|
||||
vpnHandler *stubVPNHandler
|
||||
vpnConfig *wgquick.Config
|
||||
wantErr bool
|
||||
}{
|
||||
"write wg quick file": {
|
||||
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
||||
vpnHandler: &stubVPNHandler{marshalRes: "config"},
|
||||
},
|
||||
"marshal failed": {
|
||||
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
||||
vpnHandler: &stubVPNHandler{marshalErr: errors.New("some err")},
|
||||
wantErr: true,
|
||||
},
|
||||
"write fails": {
|
||||
fileHandler: file.NewHandler(afero.NewReadOnlyFs(afero.NewMemMapFs())),
|
||||
vpnHandler: &stubVPNHandler{marshalRes: "config"},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
err := writeWGQuickFile(tc.fileHandler, tc.vpnHandler, tc.vpnConfig)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
file, err := tc.fileHandler.Read(constants.WGQuickConfigFilename)
|
||||
assert.NoError(err)
|
||||
assert.Contains(string(file), tc.vpnHandler.marshalRes)
|
||||
}
|
||||
})
|
||||
}
|
||||
func (s *stubInitServer) Init(ctx context.Context, req *initproto.InitRequest) (*initproto.InitResponse, error) {
|
||||
s.activateAutoscalingNodeGroups = req.AutoscalingNodeGroups
|
||||
return s.initResp, s.initErr
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/edgelesssys/constellation/cli/internal/proto"
|
||||
"github.com/edgelesssys/constellation/coordinator/pubapi/pubproto"
|
||||
"github.com/edgelesssys/constellation/coordinator/state"
|
||||
"github.com/edgelesssys/constellation/internal/atls"
|
||||
)
|
||||
|
||||
type protoClient interface {
|
||||
Connect(endpoint string, validators []atls.Validator) error
|
||||
Close() error
|
||||
GetState(ctx context.Context) (state.State, error)
|
||||
Activate(ctx context.Context, userPublicKey, masterSecret []byte, nodeIPs, coordinatorIPs, autoscalingNodeGroups []string, cloudServiceAccountURI string, sshUsers []*pubproto.SSHUserKey) (proto.ActivationResponseClient, error)
|
||||
}
|
|
@ -1,225 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/edgelesssys/constellation/cli/internal/proto"
|
||||
"github.com/edgelesssys/constellation/coordinator/pubapi/pubproto"
|
||||
"github.com/edgelesssys/constellation/coordinator/state"
|
||||
"github.com/edgelesssys/constellation/internal/atls"
|
||||
)
|
||||
|
||||
type stubProtoClient struct {
|
||||
conn bool
|
||||
respClient proto.ActivationResponseClient
|
||||
connectErr error
|
||||
closeErr error
|
||||
getStateErr error
|
||||
activateErr error
|
||||
|
||||
getStateState state.State
|
||||
activateUserPublicKey []byte
|
||||
activateMasterSecret []byte
|
||||
activateNodeIPs []string
|
||||
activateCoordinatorIPs []string
|
||||
activateAutoscalingNodeGroups []string
|
||||
cloudServiceAccountURI string
|
||||
sshUserKeys []*pubproto.SSHUserKey
|
||||
}
|
||||
|
||||
func (c *stubProtoClient) Connect(_ string, _ []atls.Validator) error {
|
||||
c.conn = true
|
||||
return c.connectErr
|
||||
}
|
||||
|
||||
func (c *stubProtoClient) Close() error {
|
||||
c.conn = false
|
||||
return c.closeErr
|
||||
}
|
||||
|
||||
func (c *stubProtoClient) GetState(_ context.Context) (state.State, error) {
|
||||
return c.getStateState, c.getStateErr
|
||||
}
|
||||
|
||||
func (c *stubProtoClient) Activate(ctx context.Context, userPublicKey, masterSecret []byte, nodeIPs, coordinatorIPs []string, autoscalingNodeGroups []string, cloudServiceAccountURI string, sshUserKeys []*pubproto.SSHUserKey) (proto.ActivationResponseClient, error) {
|
||||
c.activateUserPublicKey = userPublicKey
|
||||
c.activateMasterSecret = masterSecret
|
||||
c.activateNodeIPs = nodeIPs
|
||||
c.activateCoordinatorIPs = coordinatorIPs
|
||||
c.activateAutoscalingNodeGroups = autoscalingNodeGroups
|
||||
c.cloudServiceAccountURI = cloudServiceAccountURI
|
||||
c.sshUserKeys = sshUserKeys
|
||||
|
||||
return c.respClient, c.activateErr
|
||||
}
|
||||
|
||||
func (c *stubProtoClient) ActivateAdditionalCoordinators(ctx context.Context, ips []string) error {
|
||||
return c.activateErr
|
||||
}
|
||||
|
||||
type stubActivationRespClient struct {
|
||||
nextLogErr *error
|
||||
getKubeconfigErr error
|
||||
getCoordinatorVpnKeyErr error
|
||||
getClientVpnIpErr error
|
||||
getOwnerIDErr error
|
||||
getClusterIDErr error
|
||||
writeLogStreamErr error
|
||||
}
|
||||
|
||||
func (s *stubActivationRespClient) NextLog() (string, error) {
|
||||
if s.nextLogErr == nil {
|
||||
return "", io.EOF
|
||||
}
|
||||
return "", *s.nextLogErr
|
||||
}
|
||||
|
||||
func (s *stubActivationRespClient) WriteLogStream(io.Writer) error {
|
||||
return s.writeLogStreamErr
|
||||
}
|
||||
|
||||
func (s *stubActivationRespClient) GetKubeconfig() (string, error) {
|
||||
return "", s.getKubeconfigErr
|
||||
}
|
||||
|
||||
func (s *stubActivationRespClient) GetCoordinatorVpnKey() (string, error) {
|
||||
return "", s.getCoordinatorVpnKeyErr
|
||||
}
|
||||
|
||||
func (s *stubActivationRespClient) GetClientVpnIp() (string, error) {
|
||||
return "", s.getClientVpnIpErr
|
||||
}
|
||||
|
||||
func (s *stubActivationRespClient) GetOwnerID() (string, error) {
|
||||
return "", s.getOwnerIDErr
|
||||
}
|
||||
|
||||
func (s *stubActivationRespClient) GetClusterID() (string, error) {
|
||||
return "", s.getClusterIDErr
|
||||
}
|
||||
|
||||
type fakeProtoClient struct {
|
||||
conn bool
|
||||
respClient proto.ActivationResponseClient
|
||||
}
|
||||
|
||||
func (c *fakeProtoClient) Connect(endpoint string, validators []atls.Validator) error {
|
||||
if endpoint == "" {
|
||||
return errors.New("endpoint is empty")
|
||||
}
|
||||
if len(validators) == 0 {
|
||||
return errors.New("validators is empty")
|
||||
}
|
||||
c.conn = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeProtoClient) Close() error {
|
||||
c.conn = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeProtoClient) GetState(_ context.Context) (state.State, error) {
|
||||
if !c.conn {
|
||||
return state.Uninitialized, errors.New("client is not connected")
|
||||
}
|
||||
return state.IsNode, nil
|
||||
}
|
||||
|
||||
func (c *fakeProtoClient) Activate(ctx context.Context, userPublicKey, masterSecret []byte, nodeIPs, coordinatorIPs, autoscalingNodeGroups []string, cloudServiceAccountURI string, sshUserKeys []*pubproto.SSHUserKey) (proto.ActivationResponseClient, error) {
|
||||
if !c.conn {
|
||||
return nil, errors.New("client is not connected")
|
||||
}
|
||||
return c.respClient, nil
|
||||
}
|
||||
|
||||
func (c *fakeProtoClient) ActivateAdditionalCoordinators(ctx context.Context, ips []string) error {
|
||||
if !c.conn {
|
||||
return errors.New("client is not connected")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakeActivationRespClient struct {
|
||||
responses []fakeActivationRespMessage
|
||||
kubeconfig string
|
||||
coordinatorVpnKey string
|
||||
clientVpnIp string
|
||||
ownerID string
|
||||
clusterID string
|
||||
}
|
||||
|
||||
func (c *fakeActivationRespClient) NextLog() (string, error) {
|
||||
for len(c.responses) > 0 {
|
||||
resp := c.responses[0]
|
||||
c.responses = c.responses[1:]
|
||||
if len(resp.log) > 0 {
|
||||
return resp.log, nil
|
||||
}
|
||||
c.kubeconfig = resp.kubeconfig
|
||||
c.coordinatorVpnKey = resp.coordinatorVpnKey
|
||||
c.clientVpnIp = resp.clientVpnIp
|
||||
c.ownerID = resp.ownerID
|
||||
c.clusterID = resp.clusterID
|
||||
}
|
||||
return "", io.EOF
|
||||
}
|
||||
|
||||
func (c *fakeActivationRespClient) WriteLogStream(w io.Writer) error {
|
||||
log, err := c.NextLog()
|
||||
for err == nil {
|
||||
fmt.Fprint(w, log)
|
||||
log, err = c.NextLog()
|
||||
}
|
||||
if !errors.Is(err, io.EOF) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeActivationRespClient) GetKubeconfig() (string, error) {
|
||||
if c.kubeconfig == "" {
|
||||
return "", errors.New("kubeconfig is empty")
|
||||
}
|
||||
return c.kubeconfig, nil
|
||||
}
|
||||
|
||||
func (c *fakeActivationRespClient) GetCoordinatorVpnKey() (string, error) {
|
||||
if c.coordinatorVpnKey == "" {
|
||||
return "", errors.New("control-plane public VPN key is empty")
|
||||
}
|
||||
return c.coordinatorVpnKey, nil
|
||||
}
|
||||
|
||||
func (c *fakeActivationRespClient) GetClientVpnIp() (string, error) {
|
||||
if c.clientVpnIp == "" {
|
||||
return "", errors.New("client VPN IP is empty")
|
||||
}
|
||||
return c.clientVpnIp, nil
|
||||
}
|
||||
|
||||
func (c *fakeActivationRespClient) GetOwnerID() (string, error) {
|
||||
if c.ownerID == "" {
|
||||
return "", errors.New("init secret is empty")
|
||||
}
|
||||
return c.ownerID, nil
|
||||
}
|
||||
|
||||
func (c *fakeActivationRespClient) GetClusterID() (string, error) {
|
||||
if c.clusterID == "" {
|
||||
return "", errors.New("cluster identifier is empty")
|
||||
}
|
||||
return c.clusterID, nil
|
||||
}
|
||||
|
||||
type fakeActivationRespMessage struct {
|
||||
log string
|
||||
kubeconfig string
|
||||
coordinatorVpnKey string
|
||||
clientVpnIp string
|
||||
ownerID string
|
||||
clusterID string
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/edgelesssys/constellation/coordinator/state"
|
||||
"github.com/edgelesssys/constellation/internal/atls"
|
||||
)
|
||||
|
||||
type statusWaiter interface {
|
||||
InitializeValidators([]atls.Validator) error
|
||||
WaitForAll(ctx context.Context, endpoints []string, status ...state.State) error
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/edgelesssys/constellation/coordinator/state"
|
||||
"github.com/edgelesssys/constellation/internal/atls"
|
||||
)
|
||||
|
||||
type stubStatusWaiter struct {
|
||||
initialized bool
|
||||
initializeErr error
|
||||
waitForAllErr error
|
||||
}
|
||||
|
||||
func (s *stubStatusWaiter) InitializeValidators([]atls.Validator) error {
|
||||
s.initialized = true
|
||||
return s.initializeErr
|
||||
}
|
||||
|
||||
func (s *stubStatusWaiter) WaitForAll(ctx context.Context, endpoints []string, status ...state.State) error {
|
||||
if !s.initialized {
|
||||
return errors.New("waiter not initialized")
|
||||
}
|
||||
return s.waitForAllErr
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
package proto
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/edgelesssys/constellation/coordinator/pubapi/pubproto"
|
||||
"github.com/edgelesssys/constellation/coordinator/state"
|
||||
"github.com/edgelesssys/constellation/internal/atls"
|
||||
"github.com/edgelesssys/constellation/internal/grpc/atlscredentials"
|
||||
kms "github.com/edgelesssys/constellation/kms/setup"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Client wraps a PubAPI client and the connection to it.
|
||||
type Client struct {
|
||||
conn *grpc.ClientConn
|
||||
pubapi pubproto.APIClient
|
||||
}
|
||||
|
||||
// Connect connects the client to a given server, using the handed
|
||||
// Validators for the attestation of the connection.
|
||||
// The connection must be closed using Close(). If connect is
|
||||
// called on a client that already has a connection, the old
|
||||
// connection is closed.
|
||||
func (c *Client) Connect(endpoint string, validators []atls.Validator) error {
|
||||
creds := atlscredentials.New(nil, validators)
|
||||
|
||||
conn, err := grpc.Dial(endpoint, grpc.WithTransportCredentials(creds))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.conn != nil {
|
||||
c.conn.Close()
|
||||
}
|
||||
c.conn = conn
|
||||
c.pubapi = pubproto.NewAPIClient(conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the grpc connection of the client.
|
||||
// Close is idempotent and can be called on non connected clients
|
||||
// without returning an error.
|
||||
func (c *Client) Close() error {
|
||||
if c.conn == nil {
|
||||
return nil
|
||||
}
|
||||
if err := c.conn.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.conn = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetState returns the state of the connected server.
|
||||
func (c *Client) GetState(ctx context.Context) (state.State, error) {
|
||||
if c.pubapi == nil {
|
||||
return state.Uninitialized, errors.New("client is not connected")
|
||||
}
|
||||
|
||||
resp, err := c.pubapi.GetState(ctx, &pubproto.GetStateRequest{})
|
||||
if err != nil {
|
||||
return state.Uninitialized, err
|
||||
}
|
||||
return state.State(resp.State), nil
|
||||
}
|
||||
|
||||
// Activate activates the Constellation coordinator via a grpc call.
|
||||
// The handed IP addresses must be the private IP addresses of running AWS or GCP instances,
|
||||
// and the userPublicKey is the VPN key of the users WireGuard interface.
|
||||
func (c *Client) Activate(ctx context.Context, userPublicKey, masterSecret []byte, nodeIPs, coordinatorIPs, autoscalingNodeGroups []string, cloudServiceAccountURI string, sshUserKeys []*pubproto.SSHUserKey) (ActivationResponseClient, error) {
|
||||
if c.pubapi == nil {
|
||||
return nil, errors.New("client is not connected")
|
||||
}
|
||||
if len(userPublicKey) == 0 {
|
||||
return nil, errors.New("parameter userPublicKey is empty")
|
||||
}
|
||||
if len(nodeIPs) == 0 {
|
||||
return nil, errors.New("parameter ips is empty")
|
||||
}
|
||||
|
||||
pubKey, err := wgtypes.ParseKey(string(userPublicKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := &pubproto.ActivateAsCoordinatorRequest{
|
||||
AdminVpnPubKey: pubKey[:],
|
||||
NodePublicIps: nodeIPs,
|
||||
CoordinatorPublicIps: coordinatorIPs,
|
||||
AutoscalingNodeGroups: autoscalingNodeGroups,
|
||||
MasterSecret: masterSecret,
|
||||
KmsUri: kms.ClusterKMSURI,
|
||||
StorageUri: kms.NoStoreURI,
|
||||
KeyEncryptionKeyId: "",
|
||||
UseExistingKek: false,
|
||||
CloudServiceAccountUri: cloudServiceAccountURI,
|
||||
SshUserKeys: sshUserKeys,
|
||||
}
|
||||
|
||||
client, err := c.pubapi.ActivateAsCoordinator(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewActivationRespClient(client), nil
|
||||
}
|
||||
|
||||
// ActivationResponseClient has methods to read messages from a stream of
|
||||
// ActivateAsCoordinatorResponses.
|
||||
type ActivationResponseClient interface {
|
||||
// NextLog reads responses from the response stream and returns the
|
||||
// first received log.
|
||||
// If AdminConfig responses are received before the first log response
|
||||
// occurs, the state of the client is updated with those configs. An
|
||||
// io.EOF error is returned at the end of the stream.
|
||||
NextLog() (string, error)
|
||||
|
||||
// WriteLogStream reads responses from the response stream and
|
||||
// writes log responses to the handed writer.
|
||||
// Occurring AdminConfig responses update the state of the client.
|
||||
WriteLogStream(io.Writer) error
|
||||
|
||||
// GetKubeconfig returns the kubeconfig that was received in the
|
||||
// latest AdminConfig response or an error if the field is empty.
|
||||
GetKubeconfig() (string, error)
|
||||
|
||||
// GetCoordinatorVpnKey returns the Coordinator's VPN key that was
|
||||
// received in the latest AdminConfig response or an error if the field
|
||||
// is empty.
|
||||
GetCoordinatorVpnKey() (string, error)
|
||||
|
||||
// GetClientVpnIp returns the client VPN IP that was received
|
||||
// in the latest AdminConfig response or an error if the field is empty.
|
||||
GetClientVpnIp() (string, error)
|
||||
|
||||
// GetOwnerID returns the owner identifier, derived from the client's master secret
|
||||
// or an error if the field is empty.
|
||||
GetOwnerID() (string, error)
|
||||
|
||||
// GetClusterID returns the cluster's unique identifier
|
||||
// or an error if the field is empty.
|
||||
GetClusterID() (string, error)
|
||||
}
|
|
@ -1,220 +0,0 @@
|
|||
package proto
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/coordinator/pubapi/pubproto"
|
||||
"github.com/edgelesssys/constellation/coordinator/state"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/goleak"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/test/bufconn"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
goleak.VerifyTestMain(m,
|
||||
// https://github.com/census-instrumentation/opencensus-go/issues/1262
|
||||
goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"),
|
||||
)
|
||||
}
|
||||
|
||||
func TestClose(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
client := Client{}
|
||||
|
||||
// Create a connection.
|
||||
listener := bufconn.Listen(4)
|
||||
defer listener.Close()
|
||||
ctx := context.Background()
|
||||
conn, err := grpc.DialContext(ctx, "", grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||
return listener.Dial()
|
||||
}), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
require.NoError(err)
|
||||
defer conn.Close()
|
||||
|
||||
// Wait for connection to reach 'connecting' state.
|
||||
// Connection is not yet usable in this state, but we just need
|
||||
// any stable non 'shutdown' state to validate that the state
|
||||
// previous to calling close isn't already 'shutdown'.
|
||||
err = func() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer cancel()
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
if connectivity.Connecting == conn.GetState() {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
require.NoError(err)
|
||||
|
||||
client.conn = conn
|
||||
|
||||
// Close connection.
|
||||
assert.NoError(client.Close())
|
||||
assert.Empty(client.conn)
|
||||
assert.Equal(connectivity.Shutdown, conn.GetState())
|
||||
|
||||
// Close closed connection.
|
||||
assert.NoError(client.Close())
|
||||
assert.Empty(client.conn)
|
||||
assert.Equal(connectivity.Shutdown, conn.GetState())
|
||||
}
|
||||
|
||||
func TestGetState(t *testing.T) {
|
||||
someErr := errors.New("some error")
|
||||
|
||||
testCases := map[string]struct {
|
||||
pubAPIClient pubproto.APIClient
|
||||
wantErr bool
|
||||
wantState state.State
|
||||
}{
|
||||
"success": {
|
||||
pubAPIClient: &stubPubAPIClient{getStateState: state.IsNode},
|
||||
wantState: state.IsNode,
|
||||
},
|
||||
"getState error": {
|
||||
pubAPIClient: &stubPubAPIClient{getStateErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"uninitialized": {
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client := Client{}
|
||||
if tc.pubAPIClient != nil {
|
||||
client.pubapi = tc.pubAPIClient
|
||||
}
|
||||
|
||||
state, err := client.GetState(context.Background())
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.wantState, state)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivate(t *testing.T) {
|
||||
testKey := base64.StdEncoding.EncodeToString([]byte("32bytesWireGuardKeyForTheTesting"))
|
||||
someErr := errors.New("failed")
|
||||
|
||||
testCases := map[string]struct {
|
||||
pubAPIClient *stubPubAPIClient
|
||||
userPublicKey string
|
||||
ips []string
|
||||
wantErr bool
|
||||
}{
|
||||
"normal activation": {
|
||||
pubAPIClient: &stubPubAPIClient{},
|
||||
userPublicKey: testKey,
|
||||
ips: []string{"192.0.2.1", "192.0.2.1", "192.0.2.1"},
|
||||
wantErr: false,
|
||||
},
|
||||
"client without pubAPIClient": {
|
||||
userPublicKey: testKey,
|
||||
ips: []string{"192.0.2.1", "192.0.2.1", "192.0.2.1"},
|
||||
wantErr: true,
|
||||
},
|
||||
"empty public key parameter": {
|
||||
pubAPIClient: &stubPubAPIClient{},
|
||||
userPublicKey: "",
|
||||
ips: []string{"192.0.2.1", "192.0.2.1", "192.0.2.1"},
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid public key parameter": {
|
||||
pubAPIClient: &stubPubAPIClient{},
|
||||
userPublicKey: "invalid Key",
|
||||
ips: []string{"192.0.2.1", "192.0.2.1", "192.0.2.1"},
|
||||
wantErr: true,
|
||||
},
|
||||
"empty ips parameter": {
|
||||
pubAPIClient: &stubPubAPIClient{},
|
||||
userPublicKey: testKey,
|
||||
ips: []string{},
|
||||
wantErr: true,
|
||||
},
|
||||
"fail ActivateAsCoordinator": {
|
||||
pubAPIClient: &stubPubAPIClient{activateAsCoordinatorErr: someErr},
|
||||
userPublicKey: testKey,
|
||||
ips: []string{"192.0.2.1", "192.0.2.1", "192.0.2.1"},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client := Client{}
|
||||
if tc.pubAPIClient != nil {
|
||||
client.pubapi = tc.pubAPIClient
|
||||
}
|
||||
_, err := client.Activate(context.Background(), []byte(tc.userPublicKey), []byte("Constellation"), tc.ips, nil, nil, "serviceaccount://test", nil)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal("32bytesWireGuardKeyForTheTesting", string(tc.pubAPIClient.activateAsCoordinatorReqKey))
|
||||
assert.Equal(tc.ips, tc.pubAPIClient.activateAsCoordinatorReqIPs)
|
||||
assert.Equal("Constellation", string(tc.pubAPIClient.activateAsCoordinatorMasterSecret))
|
||||
assert.Equal("serviceaccount://test", tc.pubAPIClient.activateCloudServiceAccountURI)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type stubPubAPIClient struct {
|
||||
getStateState state.State
|
||||
getStateErr error
|
||||
activateAsCoordinatorErr error
|
||||
activateAdditionalNodesErr error
|
||||
activateAsCoordinatorReqKey []byte
|
||||
activateAsCoordinatorReqIPs []string
|
||||
activateAsCoordinatorMasterSecret []byte
|
||||
activateAdditionalNodesReqIPs []string
|
||||
activateCloudServiceAccountURI string
|
||||
pubproto.APIClient
|
||||
}
|
||||
|
||||
func (s *stubPubAPIClient) GetState(ctx context.Context, in *pubproto.GetStateRequest, opts ...grpc.CallOption) (*pubproto.GetStateResponse, error) {
|
||||
return &pubproto.GetStateResponse{State: uint32(s.getStateState)}, s.getStateErr
|
||||
}
|
||||
|
||||
func (s *stubPubAPIClient) ActivateAsCoordinator(ctx context.Context, in *pubproto.ActivateAsCoordinatorRequest,
|
||||
opts ...grpc.CallOption,
|
||||
) (pubproto.API_ActivateAsCoordinatorClient, error) {
|
||||
s.activateAsCoordinatorReqKey = in.AdminVpnPubKey
|
||||
s.activateAsCoordinatorReqIPs = in.NodePublicIps
|
||||
s.activateAsCoordinatorMasterSecret = in.MasterSecret
|
||||
s.activateCloudServiceAccountURI = in.CloudServiceAccountUri
|
||||
return dummyActivateAsCoordinatorClient{}, s.activateAsCoordinatorErr
|
||||
}
|
||||
|
||||
func (s *stubPubAPIClient) ActivateAdditionalNodes(ctx context.Context, in *pubproto.ActivateAdditionalNodesRequest,
|
||||
opts ...grpc.CallOption,
|
||||
) (pubproto.API_ActivateAdditionalNodesClient, error) {
|
||||
s.activateAdditionalNodesReqIPs = in.NodePublicIps
|
||||
return dummyActivateAdditionalNodesClient{}, s.activateAdditionalNodesErr
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
package proto
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/edgelesssys/constellation/coordinator/pubapi/pubproto"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
// ActivationRespClient has methods to read messages from a stream of
|
||||
// ActivateAsCoordinatorResponses. It wraps an API_ActivateAsCoordinatorClient.
|
||||
type ActivationRespClient struct {
|
||||
client pubproto.API_ActivateAsCoordinatorClient
|
||||
kubeconfig string
|
||||
coordinatorVpnKey string
|
||||
clientVpnIp string
|
||||
ownerID string
|
||||
clusterID string
|
||||
}
|
||||
|
||||
// NewActivationRespClient creates a new ActivationRespClient with the handed
|
||||
// API_ActivateAsCoordinatorClient.
|
||||
func NewActivationRespClient(client pubproto.API_ActivateAsCoordinatorClient) *ActivationRespClient {
|
||||
return &ActivationRespClient{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// NextLog reads responses from the response stream and returns the
|
||||
// first received log.
|
||||
func (a *ActivationRespClient) NextLog() (string, error) {
|
||||
for {
|
||||
resp, err := a.client.Recv()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch x := resp.Content.(type) {
|
||||
case *pubproto.ActivateAsCoordinatorResponse_Log:
|
||||
return x.Log.Message, nil
|
||||
case *pubproto.ActivateAsCoordinatorResponse_AdminConfig:
|
||||
config := x.AdminConfig
|
||||
a.kubeconfig = string(config.Kubeconfig)
|
||||
|
||||
coordinatorVpnKey, err := wgtypes.NewKey(config.CoordinatorVpnPubKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
a.coordinatorVpnKey = coordinatorVpnKey.String()
|
||||
a.clientVpnIp = config.AdminVpnIp
|
||||
a.ownerID = base64.StdEncoding.EncodeToString(config.OwnerId)
|
||||
a.clusterID = base64.StdEncoding.EncodeToString(config.ClusterId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WriteLogStream reads responses from the response stream and
|
||||
// writes log responses to the handed writer.
|
||||
func (a *ActivationRespClient) WriteLogStream(w io.Writer) error {
|
||||
log, err := a.NextLog()
|
||||
for err == nil {
|
||||
fmt.Fprintln(w, log)
|
||||
log, err = a.NextLog()
|
||||
}
|
||||
if !errors.Is(err, io.EOF) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetKubeconfig returns the kubeconfig that was received in the
|
||||
// latest AdminConfig response or an error if the field is empty.
|
||||
func (a *ActivationRespClient) GetKubeconfig() (string, error) {
|
||||
if a.kubeconfig == "" {
|
||||
return "", errors.New("kubeconfig is empty")
|
||||
}
|
||||
return a.kubeconfig, nil
|
||||
}
|
||||
|
||||
// GetCoordinatorVpnKey returns the Coordinator's VPN key that was
|
||||
// received in the latest AdminConfig response or an error if the field
|
||||
// is empty.
|
||||
func (a *ActivationRespClient) GetCoordinatorVpnKey() (string, error) {
|
||||
if a.coordinatorVpnKey == "" {
|
||||
return "", errors.New("coordinator public VPN key is empty")
|
||||
}
|
||||
return a.coordinatorVpnKey, nil
|
||||
}
|
||||
|
||||
// GetClientVpnIp returns the client VPN IP that was received
|
||||
// in the latest AdminConfig response or an error if the field is empty.
|
||||
func (a *ActivationRespClient) GetClientVpnIp() (string, error) {
|
||||
if a.clientVpnIp == "" {
|
||||
return "", errors.New("client VPN IP is empty")
|
||||
}
|
||||
return a.clientVpnIp, nil
|
||||
}
|
||||
|
||||
// GetOwnerID returns the owner identifier, derived from the client's master secret
|
||||
// or an error if the field is empty.
|
||||
func (a *ActivationRespClient) GetOwnerID() (string, error) {
|
||||
if a.ownerID == "" {
|
||||
return "", errors.New("secret identifier is empty")
|
||||
}
|
||||
return a.ownerID, nil
|
||||
}
|
||||
|
||||
// GetClusterID returns the cluster's unique identifier
|
||||
// or an error if the field is empty.
|
||||
func (a *ActivationRespClient) GetClusterID() (string, error) {
|
||||
if a.clusterID == "" {
|
||||
return "", errors.New("cluster identifier is empty")
|
||||
}
|
||||
return a.clusterID, nil
|
||||
}
|
|
@ -1,241 +0,0 @@
|
|||
package proto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/coordinator/pubapi/pubproto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// dummyActivateAsCoordinatorClient is a dummy and panics if Recv() is called.
|
||||
type dummyActivateAsCoordinatorClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (c dummyActivateAsCoordinatorClient) Recv() (*pubproto.ActivateAsCoordinatorResponse, error) {
|
||||
panic("i'm a dummy, Recv() not implemented")
|
||||
}
|
||||
|
||||
// dummyActivateAsCoordinatorClient is a dummy and panics if Recv() is called.
|
||||
type dummyActivateAdditionalNodesClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (c dummyActivateAdditionalNodesClient) Recv() (*pubproto.ActivateAdditionalNodesResponse, error) {
|
||||
panic("i'm a dummy, Recv() not implemented")
|
||||
}
|
||||
|
||||
// stubActivationAsCoordinatorClient recives responses from an predefined
|
||||
// response stream iterator or a stub error.
|
||||
type stubActivationAsCoordinatorClient struct {
|
||||
grpc.ClientStream
|
||||
|
||||
stream *stubActivateAsCoordinatorResponseIter
|
||||
recvErr error
|
||||
}
|
||||
|
||||
func (c stubActivationAsCoordinatorClient) Recv() (*pubproto.ActivateAsCoordinatorResponse, error) {
|
||||
if c.recvErr != nil {
|
||||
return nil, c.recvErr
|
||||
}
|
||||
return c.stream.Next()
|
||||
}
|
||||
|
||||
// stubActivateAsCoordinatorResponseIter is an iterator over a slice of
|
||||
// ActivateAsCoordinatorResponses. It returns the messages in the order
|
||||
// they occur in the slice and returns an io.EOF error when no response
|
||||
// is left.
|
||||
type stubActivateAsCoordinatorResponseIter struct {
|
||||
msgs []*pubproto.ActivateAsCoordinatorResponse
|
||||
}
|
||||
|
||||
// Next returns the next message from the message slice or an io.EOF error
|
||||
// if the message slice is empty.
|
||||
func (q *stubActivateAsCoordinatorResponseIter) Next() (*pubproto.ActivateAsCoordinatorResponse, error) {
|
||||
if len(q.msgs) == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
msg := q.msgs[0]
|
||||
q.msgs = q.msgs[1:]
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func TestNextLog(t *testing.T) {
|
||||
testClientVpnIp := "192.0.2.1"
|
||||
testCoordinatorVpnKey := []byte("32bytesWireGuardKeyForTheTesting")
|
||||
testCoordinatorVpnKey64 := []byte("MzJieXRlc1dpcmVHdWFyZEtleUZvclRoZVRlc3Rpbmc=")
|
||||
testKubeconfig := []byte("apiVersion:v1 kind:Config...")
|
||||
testConfigResp := &pubproto.ActivateAsCoordinatorResponse{
|
||||
Content: &pubproto.ActivateAsCoordinatorResponse_AdminConfig{
|
||||
AdminConfig: &pubproto.AdminConfig{
|
||||
AdminVpnIp: testClientVpnIp,
|
||||
CoordinatorVpnPubKey: testCoordinatorVpnKey,
|
||||
Kubeconfig: testKubeconfig,
|
||||
},
|
||||
},
|
||||
}
|
||||
testLogMessage := "some log message"
|
||||
testLogResp := &pubproto.ActivateAsCoordinatorResponse{
|
||||
Content: &pubproto.ActivateAsCoordinatorResponse_Log{
|
||||
Log: &pubproto.Log{
|
||||
Message: testLogMessage,
|
||||
},
|
||||
},
|
||||
}
|
||||
someErr := errors.New("failed")
|
||||
|
||||
testCases := map[string]struct {
|
||||
msgs []*pubproto.ActivateAsCoordinatorResponse
|
||||
wantLogLen int
|
||||
wantState bool
|
||||
recvErr error
|
||||
wantErr bool
|
||||
}{
|
||||
"some logs": {
|
||||
msgs: []*pubproto.ActivateAsCoordinatorResponse{testLogResp, testLogResp, testLogResp},
|
||||
wantLogLen: 3,
|
||||
},
|
||||
"only admin config": {
|
||||
msgs: []*pubproto.ActivateAsCoordinatorResponse{testConfigResp},
|
||||
wantState: true,
|
||||
},
|
||||
"logs and configs": {
|
||||
msgs: []*pubproto.ActivateAsCoordinatorResponse{testLogResp, testConfigResp, testLogResp, testConfigResp},
|
||||
wantLogLen: 2,
|
||||
wantState: true,
|
||||
},
|
||||
"no response": {
|
||||
msgs: []*pubproto.ActivateAsCoordinatorResponse{},
|
||||
wantLogLen: 0,
|
||||
},
|
||||
"recv fail": {
|
||||
recvErr: someErr,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
respClient := stubActivationAsCoordinatorClient{
|
||||
stream: &stubActivateAsCoordinatorResponseIter{
|
||||
msgs: tc.msgs,
|
||||
},
|
||||
recvErr: tc.recvErr,
|
||||
}
|
||||
client := NewActivationRespClient(respClient)
|
||||
|
||||
var logs []string
|
||||
var err error
|
||||
for err == nil {
|
||||
var log string
|
||||
log, err = client.NextLog()
|
||||
if err == nil {
|
||||
logs = append(logs, log)
|
||||
}
|
||||
}
|
||||
|
||||
assert.Error(err)
|
||||
if tc.wantErr {
|
||||
assert.NotErrorIs(err, io.EOF)
|
||||
return
|
||||
}
|
||||
|
||||
assert.ErrorIs(err, io.EOF)
|
||||
assert.Len(logs, tc.wantLogLen)
|
||||
|
||||
if tc.wantState {
|
||||
ip, err := client.GetClientVpnIp()
|
||||
assert.NoError(err)
|
||||
assert.Equal(testClientVpnIp, ip)
|
||||
config, err := client.GetKubeconfig()
|
||||
assert.NoError(err)
|
||||
assert.Equal(string(testKubeconfig), config)
|
||||
key, err := client.GetCoordinatorVpnKey()
|
||||
assert.NoError(err)
|
||||
assert.Equal(string(testCoordinatorVpnKey64), key)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintLogStream(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
//
|
||||
// 10 logs a 10 byte
|
||||
//
|
||||
var msgs []*pubproto.ActivateAsCoordinatorResponse
|
||||
for i := 0; i < 10; i++ {
|
||||
msgs = append(msgs, &pubproto.ActivateAsCoordinatorResponse{
|
||||
Content: &pubproto.ActivateAsCoordinatorResponse_Log{
|
||||
Log: &pubproto.Log{
|
||||
Message: "10BytesLog",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
respClient := stubActivationAsCoordinatorClient{
|
||||
stream: &stubActivateAsCoordinatorResponseIter{
|
||||
msgs: msgs,
|
||||
},
|
||||
}
|
||||
client := NewActivationRespClient(respClient)
|
||||
out := &bytes.Buffer{}
|
||||
assert.NoError(client.WriteLogStream(out))
|
||||
assert.Equal(out.Len(), 10*11) // 10 messages * (len(message) + 1 newline)
|
||||
|
||||
//
|
||||
// Check error handling.
|
||||
//
|
||||
someErr := errors.New("failed")
|
||||
respClient = stubActivationAsCoordinatorClient{
|
||||
recvErr: someErr,
|
||||
}
|
||||
client = NewActivationRespClient(respClient)
|
||||
assert.Error(client.WriteLogStream(&bytes.Buffer{}))
|
||||
}
|
||||
|
||||
func TestGetKubeconfig(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client := NewActivationRespClient(dummyActivateAsCoordinatorClient{})
|
||||
_, err := client.GetKubeconfig()
|
||||
assert.Error(err)
|
||||
|
||||
client.kubeconfig = "apiVersion:v1 kind:Config..."
|
||||
config, err := client.GetKubeconfig()
|
||||
assert.NoError(err)
|
||||
assert.Equal("apiVersion:v1 kind:Config...", config)
|
||||
}
|
||||
|
||||
func TestGetCoordinatorVpnKey(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client := NewActivationRespClient(dummyActivateAsCoordinatorClient{})
|
||||
_, err := client.GetCoordinatorVpnKey()
|
||||
assert.Error(err)
|
||||
|
||||
client.coordinatorVpnKey = "32bytesWireGuardKeyForTheTesting"
|
||||
key, err := client.GetCoordinatorVpnKey()
|
||||
assert.NoError(err)
|
||||
assert.Equal("32bytesWireGuardKeyForTheTesting", key)
|
||||
}
|
||||
|
||||
func TestGetClientVpnIp(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
client := NewActivationRespClient(dummyActivateAsCoordinatorClient{})
|
||||
_, err := client.GetClientVpnIp()
|
||||
assert.Error(err)
|
||||
|
||||
client.clientVpnIp = "192.0.2.1"
|
||||
ip, err := client.GetClientVpnIp()
|
||||
assert.NoError(err)
|
||||
assert.Equal("192.0.2.1", ip)
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
package vpn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
wgquick "github.com/nmiculinic/wg-quick-go"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
const (
|
||||
interfaceName = "wg0"
|
||||
wireguardPort = 51820
|
||||
)
|
||||
|
||||
type ConfigHandler struct {
|
||||
up func(cfg *wgquick.Config, iface string) error
|
||||
}
|
||||
|
||||
func NewConfigHandler() *ConfigHandler {
|
||||
return &ConfigHandler{up: wgquick.Up}
|
||||
}
|
||||
|
||||
func (h *ConfigHandler) Create(coordinatorPubKey, coordinatorPubIP, clientPrivKey, clientVPNIP string, mtu int) (*wgquick.Config, error) {
|
||||
return NewWGQuickConfig(coordinatorPubKey, coordinatorPubIP, clientPrivKey, clientVPNIP, mtu)
|
||||
}
|
||||
|
||||
// Apply applies the generated WireGuard quick config.
|
||||
func (h *ConfigHandler) Apply(conf *wgquick.Config) error {
|
||||
return h.up(conf, interfaceName)
|
||||
}
|
||||
|
||||
// GetBytes returns the the bytes of the config.
|
||||
func (h *ConfigHandler) Marshal(conf *wgquick.Config) ([]byte, error) {
|
||||
data, err := conf.MarshalText()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal wg-quick config: %w", err)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// newConfig creates a new WireGuard configuration.
|
||||
func newConfig(coordinatorPubKey, coordinatorPubIP, clientPrivKey string) (wgtypes.Config, error) {
|
||||
_, allowedIPs, err := net.ParseCIDR("10.118.0.1/32")
|
||||
if err != nil {
|
||||
return wgtypes.Config{}, fmt.Errorf("parsing CIDR: %w", err)
|
||||
}
|
||||
|
||||
coordinatorPubKeyParsed, err := wgtypes.ParseKey(coordinatorPubKey)
|
||||
if err != nil {
|
||||
return wgtypes.Config{}, fmt.Errorf("parsing coordinator public key: %w", err)
|
||||
}
|
||||
|
||||
var endpoint *net.UDPAddr
|
||||
if ip := net.ParseIP(coordinatorPubIP); ip != nil {
|
||||
endpoint = &net.UDPAddr{IP: ip, Port: wireguardPort}
|
||||
} else {
|
||||
endpoint = nil
|
||||
}
|
||||
clientPrivKeyParsed, err := wgtypes.ParseKey(clientPrivKey)
|
||||
if err != nil {
|
||||
return wgtypes.Config{}, fmt.Errorf("parsing client private key: %w", err)
|
||||
}
|
||||
listenPort := wireguardPort
|
||||
|
||||
keepAlive := 10 * time.Second
|
||||
return wgtypes.Config{
|
||||
PrivateKey: &clientPrivKeyParsed,
|
||||
ListenPort: &listenPort,
|
||||
ReplacePeers: false,
|
||||
Peers: []wgtypes.PeerConfig{
|
||||
{
|
||||
PublicKey: coordinatorPubKeyParsed,
|
||||
UpdateOnly: false,
|
||||
Endpoint: endpoint,
|
||||
AllowedIPs: []net.IPNet{*allowedIPs},
|
||||
PersistentKeepaliveInterval: &keepAlive,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewWGQuickConfig create a new WireGuard wg-quick configuration file and mashals it to bytes.
|
||||
func NewWGQuickConfig(coordinatorPubKey, coordinatorPubIP, clientPrivKey, clientVPNIP string, mtu int) (*wgquick.Config, error) {
|
||||
config, err := newConfig(coordinatorPubKey, coordinatorPubIP, clientPrivKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientIP := net.ParseIP(clientVPNIP)
|
||||
if clientIP == nil {
|
||||
return nil, fmt.Errorf("invalid client vpn ip '%s'", clientVPNIP)
|
||||
}
|
||||
quickfile := wgquick.Config{
|
||||
Config: config,
|
||||
Address: []net.IPNet{{IP: clientIP, Mask: []byte{255, 255, 0, 0}}},
|
||||
MTU: mtu,
|
||||
}
|
||||
return &quickfile, nil
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
package vpn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
wgquick "github.com/nmiculinic/wg-quick-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/goleak"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
goleak.VerifyTestMain(m)
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
testKey, err := wgtypes.GeneratePrivateKey()
|
||||
require.NoError(err)
|
||||
|
||||
testCases := map[string]struct {
|
||||
coordinatorPubKey string
|
||||
coordinatorPubIP string
|
||||
clientPrivKey string
|
||||
clientVPNIP string
|
||||
wantErr bool
|
||||
}{
|
||||
"valid config": {
|
||||
clientPrivKey: testKey.String(),
|
||||
clientVPNIP: "192.0.2.1",
|
||||
coordinatorPubKey: testKey.PublicKey().String(),
|
||||
coordinatorPubIP: "192.0.2.1",
|
||||
},
|
||||
"valid missing endpoint": {
|
||||
clientPrivKey: testKey.String(),
|
||||
clientVPNIP: "192.0.2.1",
|
||||
coordinatorPubKey: testKey.PublicKey().String(),
|
||||
},
|
||||
"invalid coordinator pub key": {
|
||||
clientPrivKey: testKey.String(),
|
||||
clientVPNIP: "192.0.2.1",
|
||||
coordinatorPubIP: "192.0.2.1",
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid client priv key": {
|
||||
clientVPNIP: "192.0.2.1",
|
||||
coordinatorPubKey: testKey.PublicKey().String(),
|
||||
coordinatorPubIP: "192.0.2.1",
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid client ip": {
|
||||
clientPrivKey: testKey.String(),
|
||||
coordinatorPubKey: testKey.PublicKey().String(),
|
||||
coordinatorPubIP: "192.0.2.1",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
handler := &ConfigHandler{}
|
||||
const mtu = 2
|
||||
|
||||
quickConfig, err := handler.Create(tc.coordinatorPubKey, tc.coordinatorPubIP, tc.clientPrivKey, tc.clientVPNIP, mtu)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.clientPrivKey, quickConfig.PrivateKey.String())
|
||||
assert.Equal(tc.clientVPNIP, quickConfig.Address[0].IP.String())
|
||||
|
||||
if tc.coordinatorPubIP != "" {
|
||||
assert.Equal(tc.coordinatorPubIP, quickConfig.Peers[0].Endpoint.IP.String())
|
||||
}
|
||||
assert.Equal(mtu, quickConfig.MTU)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApply(t *testing.T) {
|
||||
testKey, err := wgtypes.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := map[string]struct {
|
||||
quickConfig *wgquick.Config
|
||||
upErr error
|
||||
wantErr bool
|
||||
}{
|
||||
"valid": {
|
||||
quickConfig: &wgquick.Config{Config: wgtypes.Config{PrivateKey: &testKey}},
|
||||
},
|
||||
"invalid apply": {
|
||||
quickConfig: &wgquick.Config{Config: wgtypes.Config{PrivateKey: &testKey}},
|
||||
upErr: errors.New("some err"),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
var ifaceSpy string
|
||||
var cfgSpy *wgquick.Config
|
||||
upSpy := func(cfg *wgquick.Config, iface string) error {
|
||||
ifaceSpy = iface
|
||||
cfgSpy = cfg
|
||||
return tc.upErr
|
||||
}
|
||||
|
||||
handler := &ConfigHandler{up: upSpy}
|
||||
|
||||
err := handler.Apply(tc.quickConfig)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal(interfaceName, ifaceSpy)
|
||||
assert.Equal(tc.quickConfig, cfgSpy)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
testKey, err := wgtypes.GeneratePrivateKey()
|
||||
require.NoError(err)
|
||||
|
||||
testCases := map[string]struct {
|
||||
quickConfig *wgquick.Config
|
||||
wantErr bool
|
||||
}{
|
||||
"valid": {
|
||||
quickConfig: &wgquick.Config{Config: wgtypes.Config{PrivateKey: &testKey}},
|
||||
},
|
||||
"invalid config": {
|
||||
quickConfig: &wgquick.Config{Config: wgtypes.Config{}},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
handler := &ConfigHandler{}
|
||||
|
||||
data, err := handler.Marshal(tc.quickConfig)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Greater(len(data), 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue