mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-02-08 02:55:33 -05:00
cli: add ssh
command to securely connect with nodes over ssh (#3568)
This commit is contained in:
parent
e6048e093b
commit
706d1dff15
@ -11,6 +11,7 @@ go_library(
|
|||||||
"//bootstrapper/internal/journald",
|
"//bootstrapper/internal/journald",
|
||||||
"//internal/atls",
|
"//internal/atls",
|
||||||
"//internal/attestation",
|
"//internal/attestation",
|
||||||
|
"//internal/constants",
|
||||||
"//internal/crypto",
|
"//internal/crypto",
|
||||||
"//internal/file",
|
"//internal/file",
|
||||||
"//internal/grpc/atlscredentials",
|
"//internal/grpc/atlscredentials",
|
||||||
@ -26,6 +27,7 @@ go_library(
|
|||||||
"@org_golang_google_grpc//keepalive",
|
"@org_golang_google_grpc//keepalive",
|
||||||
"@org_golang_google_grpc//status",
|
"@org_golang_google_grpc//status",
|
||||||
"@org_golang_x_crypto//bcrypt",
|
"@org_golang_x_crypto//bcrypt",
|
||||||
|
"@org_golang_x_crypto//ssh",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ package initserver
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ed25519"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -33,6 +34,7 @@ import (
|
|||||||
"github.com/edgelesssys/constellation/v2/bootstrapper/internal/journald"
|
"github.com/edgelesssys/constellation/v2/bootstrapper/internal/journald"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/atls"
|
"github.com/edgelesssys/constellation/v2/internal/atls"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/crypto"
|
"github.com/edgelesssys/constellation/v2/internal/crypto"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/grpc/atlscredentials"
|
"github.com/edgelesssys/constellation/v2/internal/grpc/atlscredentials"
|
||||||
@ -44,6 +46,7 @@ import (
|
|||||||
"github.com/edgelesssys/constellation/v2/internal/role"
|
"github.com/edgelesssys/constellation/v2/internal/role"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/versions/components"
|
"github.com/edgelesssys/constellation/v2/internal/versions/components"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
@ -222,6 +225,28 @@ func (s *Server) Init(req *initproto.InitRequest, stream initproto.API_InitServe
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Derive the emergency ssh CA key
|
||||||
|
key, err := cloudKms.GetDEK(stream.Context(), crypto.DEKPrefix+constants.SSHCAKeySuffix, ed25519.SeedSize)
|
||||||
|
if err != nil {
|
||||||
|
if e := s.sendLogsWithMessage(stream, status.Errorf(codes.Internal, "retrieving DEK for key derivation: %s", err)); e != nil {
|
||||||
|
err = errors.Join(err, e)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ca, err := crypto.GenerateEmergencySSHCAKey(key)
|
||||||
|
if err != nil {
|
||||||
|
if e := s.sendLogsWithMessage(stream, status.Errorf(codes.Internal, "generating emergency SSH CA key: %s", err)); e != nil {
|
||||||
|
err = errors.Join(err, e)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.fileHandler.Write(constants.SSHCAKeyPath, ssh.MarshalAuthorizedKey(ca.PublicKey()), file.OptMkdirAll); err != nil {
|
||||||
|
if e := s.sendLogsWithMessage(stream, status.Errorf(codes.Internal, "writing ssh CA pubkey: %s", err)); e != nil {
|
||||||
|
err = errors.Join(err, e)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
clusterName := req.ClusterName
|
clusterName := req.ClusterName
|
||||||
if clusterName == "" {
|
if clusterName == "" {
|
||||||
clusterName = "constellation"
|
clusterName = "constellation"
|
||||||
|
@ -271,6 +271,10 @@ func (c *JoinClient) startNodeAndJoin(ticket *joinproto.IssueJoinTicketResponse,
|
|||||||
return fmt.Errorf("writing kubelet key: %w", err)
|
return fmt.Errorf("writing kubelet key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := c.fileHandler.Write(constants.SSHCAKeyPath, ticket.AuthorizedCaPublicKey, file.OptMkdirAll); err != nil {
|
||||||
|
return fmt.Errorf("writing ssh ca key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
state := nodestate.NodeState{
|
state := nodestate.NodeState{
|
||||||
Role: c.role,
|
Role: c.role,
|
||||||
MeasurementSalt: ticket.MeasurementSalt,
|
MeasurementSalt: ticket.MeasurementSalt,
|
||||||
|
@ -50,6 +50,8 @@ func TestClient(t *testing.T) {
|
|||||||
{Role: role.ControlPlane, Name: "node-4", VPCIP: "192.0.2.2"},
|
{Role: role.ControlPlane, Name: "node-4", VPCIP: "192.0.2.2"},
|
||||||
{Role: role.ControlPlane, Name: "node-5", VPCIP: "192.0.2.3"},
|
{Role: role.ControlPlane, Name: "node-5", VPCIP: "192.0.2.3"},
|
||||||
}
|
}
|
||||||
|
caDerivationKey := make([]byte, 256)
|
||||||
|
respCaKey := &joinproto.IssueJoinTicketResponse{AuthorizedCaPublicKey: caDerivationKey}
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
role role.Role
|
role role.Role
|
||||||
@ -69,7 +71,7 @@ func TestClient(t *testing.T) {
|
|||||||
selfAnswer{err: assert.AnError},
|
selfAnswer{err: assert.AnError},
|
||||||
selfAnswer{instance: workerSelf},
|
selfAnswer{instance: workerSelf},
|
||||||
listAnswer{instances: peers},
|
listAnswer{instances: peers},
|
||||||
issueJoinTicketAnswer{},
|
issueJoinTicketAnswer{resp: respCaKey},
|
||||||
},
|
},
|
||||||
clusterJoiner: &stubClusterJoiner{},
|
clusterJoiner: &stubClusterJoiner{},
|
||||||
nodeLock: newFakeLock(),
|
nodeLock: newFakeLock(),
|
||||||
@ -85,7 +87,7 @@ func TestClient(t *testing.T) {
|
|||||||
selfAnswer{instance: metadata.InstanceMetadata{Name: "node-1"}},
|
selfAnswer{instance: metadata.InstanceMetadata{Name: "node-1"}},
|
||||||
selfAnswer{instance: workerSelf},
|
selfAnswer{instance: workerSelf},
|
||||||
listAnswer{instances: peers},
|
listAnswer{instances: peers},
|
||||||
issueJoinTicketAnswer{},
|
issueJoinTicketAnswer{resp: respCaKey},
|
||||||
},
|
},
|
||||||
clusterJoiner: &stubClusterJoiner{},
|
clusterJoiner: &stubClusterJoiner{},
|
||||||
nodeLock: newFakeLock(),
|
nodeLock: newFakeLock(),
|
||||||
@ -101,7 +103,7 @@ func TestClient(t *testing.T) {
|
|||||||
listAnswer{err: assert.AnError},
|
listAnswer{err: assert.AnError},
|
||||||
listAnswer{err: assert.AnError},
|
listAnswer{err: assert.AnError},
|
||||||
listAnswer{instances: peers},
|
listAnswer{instances: peers},
|
||||||
issueJoinTicketAnswer{},
|
issueJoinTicketAnswer{resp: respCaKey},
|
||||||
},
|
},
|
||||||
clusterJoiner: &stubClusterJoiner{},
|
clusterJoiner: &stubClusterJoiner{},
|
||||||
nodeLock: newFakeLock(),
|
nodeLock: newFakeLock(),
|
||||||
@ -117,7 +119,7 @@ func TestClient(t *testing.T) {
|
|||||||
listAnswer{},
|
listAnswer{},
|
||||||
listAnswer{},
|
listAnswer{},
|
||||||
listAnswer{instances: peers},
|
listAnswer{instances: peers},
|
||||||
issueJoinTicketAnswer{},
|
issueJoinTicketAnswer{resp: respCaKey},
|
||||||
},
|
},
|
||||||
clusterJoiner: &stubClusterJoiner{},
|
clusterJoiner: &stubClusterJoiner{},
|
||||||
nodeLock: newFakeLock(),
|
nodeLock: newFakeLock(),
|
||||||
@ -134,7 +136,7 @@ func TestClient(t *testing.T) {
|
|||||||
listAnswer{instances: peers},
|
listAnswer{instances: peers},
|
||||||
issueJoinTicketAnswer{err: assert.AnError},
|
issueJoinTicketAnswer{err: assert.AnError},
|
||||||
listAnswer{instances: peers},
|
listAnswer{instances: peers},
|
||||||
issueJoinTicketAnswer{},
|
issueJoinTicketAnswer{resp: respCaKey},
|
||||||
},
|
},
|
||||||
clusterJoiner: &stubClusterJoiner{},
|
clusterJoiner: &stubClusterJoiner{},
|
||||||
nodeLock: newFakeLock(),
|
nodeLock: newFakeLock(),
|
||||||
@ -151,7 +153,7 @@ func TestClient(t *testing.T) {
|
|||||||
listAnswer{instances: peers},
|
listAnswer{instances: peers},
|
||||||
issueJoinTicketAnswer{err: assert.AnError},
|
issueJoinTicketAnswer{err: assert.AnError},
|
||||||
listAnswer{instances: peers},
|
listAnswer{instances: peers},
|
||||||
issueJoinTicketAnswer{},
|
issueJoinTicketAnswer{resp: respCaKey},
|
||||||
},
|
},
|
||||||
clusterJoiner: &stubClusterJoiner{},
|
clusterJoiner: &stubClusterJoiner{},
|
||||||
nodeLock: newFakeLock(),
|
nodeLock: newFakeLock(),
|
||||||
@ -164,7 +166,7 @@ func TestClient(t *testing.T) {
|
|||||||
apiAnswers: []any{
|
apiAnswers: []any{
|
||||||
selfAnswer{instance: controlSelf},
|
selfAnswer{instance: controlSelf},
|
||||||
listAnswer{instances: peers},
|
listAnswer{instances: peers},
|
||||||
issueJoinTicketAnswer{},
|
issueJoinTicketAnswer{resp: respCaKey},
|
||||||
},
|
},
|
||||||
clusterJoiner: &stubClusterJoiner{numBadCalls: -1, joinClusterErr: assert.AnError},
|
clusterJoiner: &stubClusterJoiner{numBadCalls: -1, joinClusterErr: assert.AnError},
|
||||||
nodeLock: newFakeLock(),
|
nodeLock: newFakeLock(),
|
||||||
@ -177,7 +179,7 @@ func TestClient(t *testing.T) {
|
|||||||
apiAnswers: []any{
|
apiAnswers: []any{
|
||||||
selfAnswer{instance: controlSelf},
|
selfAnswer{instance: controlSelf},
|
||||||
listAnswer{instances: peers},
|
listAnswer{instances: peers},
|
||||||
issueJoinTicketAnswer{},
|
issueJoinTicketAnswer{resp: respCaKey},
|
||||||
},
|
},
|
||||||
clusterJoiner: &stubClusterJoiner{numBadCalls: 1, joinClusterErr: assert.AnError},
|
clusterJoiner: &stubClusterJoiner{numBadCalls: 1, joinClusterErr: assert.AnError},
|
||||||
nodeLock: newFakeLock(),
|
nodeLock: newFakeLock(),
|
||||||
@ -191,7 +193,7 @@ func TestClient(t *testing.T) {
|
|||||||
apiAnswers: []any{
|
apiAnswers: []any{
|
||||||
selfAnswer{instance: controlSelf},
|
selfAnswer{instance: controlSelf},
|
||||||
listAnswer{instances: peers},
|
listAnswer{instances: peers},
|
||||||
issueJoinTicketAnswer{},
|
issueJoinTicketAnswer{resp: respCaKey},
|
||||||
},
|
},
|
||||||
clusterJoiner: &stubClusterJoiner{},
|
clusterJoiner: &stubClusterJoiner{},
|
||||||
nodeLock: lockedLock,
|
nodeLock: lockedLock,
|
||||||
|
@ -61,6 +61,7 @@ func NewRootCmd() *cobra.Command {
|
|||||||
rootCmd.AddCommand(cmd.NewIAMCmd())
|
rootCmd.AddCommand(cmd.NewIAMCmd())
|
||||||
rootCmd.AddCommand(cmd.NewVersionCmd())
|
rootCmd.AddCommand(cmd.NewVersionCmd())
|
||||||
rootCmd.AddCommand(cmd.NewInitCmd())
|
rootCmd.AddCommand(cmd.NewInitCmd())
|
||||||
|
rootCmd.AddCommand(cmd.NewSSHCmd())
|
||||||
rootCmd.AddCommand(cmd.NewMaaPatchCmd())
|
rootCmd.AddCommand(cmd.NewMaaPatchCmd())
|
||||||
|
|
||||||
return rootCmd
|
return rootCmd
|
||||||
|
@ -37,6 +37,7 @@ go_library(
|
|||||||
"miniup_linux_amd64.go",
|
"miniup_linux_amd64.go",
|
||||||
"recover.go",
|
"recover.go",
|
||||||
"spinner.go",
|
"spinner.go",
|
||||||
|
"ssh.go",
|
||||||
"status.go",
|
"status.go",
|
||||||
"terminate.go",
|
"terminate.go",
|
||||||
"upgrade.go",
|
"upgrade.go",
|
||||||
@ -116,6 +117,8 @@ go_library(
|
|||||||
"//internal/attestation/azure/tdx",
|
"//internal/attestation/azure/tdx",
|
||||||
"@com_github_google_go_sev_guest//proto/sevsnp",
|
"@com_github_google_go_sev_guest//proto/sevsnp",
|
||||||
"@com_github_google_go_tpm_tools//proto/attest",
|
"@com_github_google_go_tpm_tools//proto/attest",
|
||||||
|
"@org_golang_x_crypto//ssh",
|
||||||
|
"//internal/kms/setup",
|
||||||
] + select({
|
] + select({
|
||||||
"@io_bazel_rules_go//go/platform:android_amd64": [
|
"@io_bazel_rules_go//go/platform:android_amd64": [
|
||||||
"@org_golang_x_sys//unix",
|
"@org_golang_x_sys//unix",
|
||||||
@ -142,6 +145,7 @@ go_test(
|
|||||||
"maapatch_test.go",
|
"maapatch_test.go",
|
||||||
"recover_test.go",
|
"recover_test.go",
|
||||||
"spinner_test.go",
|
"spinner_test.go",
|
||||||
|
"ssh_test.go",
|
||||||
"status_test.go",
|
"status_test.go",
|
||||||
"terminate_test.go",
|
"terminate_test.go",
|
||||||
"upgradeapply_test.go",
|
"upgradeapply_test.go",
|
||||||
@ -201,6 +205,7 @@ go_test(
|
|||||||
"@org_golang_google_grpc//:grpc",
|
"@org_golang_google_grpc//:grpc",
|
||||||
"@org_golang_google_grpc//codes",
|
"@org_golang_google_grpc//codes",
|
||||||
"@org_golang_google_grpc//status",
|
"@org_golang_google_grpc//status",
|
||||||
|
"@org_golang_x_crypto//ssh",
|
||||||
"@org_golang_x_mod//semver",
|
"@org_golang_x_mod//semver",
|
||||||
"@org_uber_go_goleak//:goleak",
|
"@org_uber_go_goleak//:goleak",
|
||||||
],
|
],
|
||||||
|
122
cli/internal/cmd/ssh.go
Normal file
122
cli/internal/cmd/ssh.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/crypto"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/kms/setup"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewSSHCmd returns a new cobra.Command for the ssh command.
|
||||||
|
func NewSSHCmd() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "ssh",
|
||||||
|
Short: "Prepare your cluster for emergency ssh access",
|
||||||
|
Long: "Prepare your cluster for emergency ssh access and sign a given key pair for authorization.",
|
||||||
|
Args: cobra.ExactArgs(0),
|
||||||
|
RunE: runSSH,
|
||||||
|
}
|
||||||
|
cmd.Flags().String("key", "", "the path to an existing ssh public key")
|
||||||
|
must(cmd.MarkFlagRequired("key"))
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runSSH(cmd *cobra.Command, _ []string) error {
|
||||||
|
fh := file.NewHandler(afero.NewOsFs())
|
||||||
|
debugLogger, err := newDebugFileLogger(cmd, fh)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPath, err := cmd.Flags().GetString("key")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("retrieving path to public key from flags: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeCertificateForKey(cmd, keyPath, fh, debugLogger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeCertificateForKey(cmd *cobra.Command, keyPath string, fh file.Handler, debugLogger debugLog) error {
|
||||||
|
_, err := fh.Stat(constants.TerraformWorkingDir)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("directory %q does not exist", constants.TerraformWorkingDir)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(miampf): Since other KMS aren't fully implemented yet, this commands assumes that the cKMS is used and derives the key accordingly.
|
||||||
|
var mastersecret uri.MasterSecret
|
||||||
|
if err = fh.ReadJSON(constants.MasterSecretFilename, &mastersecret); err != nil {
|
||||||
|
return fmt.Errorf("reading master secret: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mastersecretURI := uri.MasterSecret{Key: mastersecret.Key, Salt: mastersecret.Salt}
|
||||||
|
kms, err := setup.KMS(cmd.Context(), uri.NoStoreURI, mastersecretURI.EncodeToURI())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("setting up KMS: %s", err)
|
||||||
|
}
|
||||||
|
sshCAKeySeed, err := kms.GetDEK(cmd.Context(), crypto.DEKPrefix+constants.SSHCAKeySuffix, ed25519.SeedSize)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("retrieving key from KMS: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ca, err := crypto.GenerateEmergencySSHCAKey(sshCAKeySeed)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("generating ssh emergency CA key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
debugLogger.Debug("SSH CA KEY generated", "public-key", string(ssh.MarshalAuthorizedKey(ca.PublicKey())))
|
||||||
|
|
||||||
|
keyBuffer, err := fh.Read(keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("reading public key %q: %s", keyPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, _, _, _, err := ssh.ParseAuthorizedKey(keyBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parsing public key %q: %s", keyPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate := ssh.Certificate{
|
||||||
|
Key: pub,
|
||||||
|
CertType: ssh.UserCert,
|
||||||
|
ValidAfter: uint64(time.Now().Unix()),
|
||||||
|
ValidBefore: uint64(time.Now().Add(24 * time.Hour).Unix()),
|
||||||
|
ValidPrincipals: []string{"root"},
|
||||||
|
Permissions: ssh.Permissions{
|
||||||
|
Extensions: map[string]string{
|
||||||
|
"permit-port-forwarding": "yes",
|
||||||
|
"permit-pty": "yes",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := certificate.SignCert(rand.Reader, ca); err != nil {
|
||||||
|
return fmt.Errorf("signing certificate: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
debugLogger.Debug("Signed certificate", "certificate", string(ssh.MarshalAuthorizedKey(&certificate)))
|
||||||
|
if err := fh.Write(fmt.Sprintf("%s/ca_cert.pub", constants.TerraformWorkingDir), ssh.MarshalAuthorizedKey(&certificate), file.OptOverwrite); err != nil {
|
||||||
|
return fmt.Errorf("writing certificate: %s", err)
|
||||||
|
}
|
||||||
|
cmd.Printf("You can now connect to a node using 'ssh -F %s/ssh_config -i <your private key> <node ip>'.\nYou can obtain the private node IP via the web UI of your CSP.\n", constants.TerraformWorkingDir)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
114
cli/internal/cmd/ssh_test.go
Normal file
114
cli/internal/cmd/ssh_test.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSSH(t *testing.T) {
|
||||||
|
someSSHPubKey := "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDA1yYg1PIJNjAGjyuv66r8AJtpfBDFLdp3u9lVwkgbVKv1AzcaeTF/NEw+nhNJOjuCZ61LTPj12LZ8Wy/oSm0A= motte@lolcatghost"
|
||||||
|
someSSHPubKeyPath := "some-key.pub"
|
||||||
|
someMasterSecret := `
|
||||||
|
{
|
||||||
|
"key": "MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAK",
|
||||||
|
"salt": "MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAK"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
newFsWithDirectory := func() file.Handler {
|
||||||
|
require := require.New(t)
|
||||||
|
fh := file.NewHandler(afero.NewMemMapFs())
|
||||||
|
require.NoError(fh.MkdirAll(constants.TerraformWorkingDir))
|
||||||
|
return fh
|
||||||
|
}
|
||||||
|
newFsNoDirectory := func() file.Handler {
|
||||||
|
fh := file.NewHandler(afero.NewMemMapFs())
|
||||||
|
return fh
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
fh file.Handler
|
||||||
|
pubKey string
|
||||||
|
masterSecret string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"everything exists": {
|
||||||
|
fh: newFsWithDirectory(),
|
||||||
|
pubKey: someSSHPubKey,
|
||||||
|
masterSecret: someMasterSecret,
|
||||||
|
},
|
||||||
|
"no public key": {
|
||||||
|
fh: newFsWithDirectory(),
|
||||||
|
masterSecret: someMasterSecret,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"no master secret": {
|
||||||
|
fh: newFsWithDirectory(),
|
||||||
|
pubKey: someSSHPubKey,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"malformed public key": {
|
||||||
|
fh: newFsWithDirectory(),
|
||||||
|
pubKey: "asdf",
|
||||||
|
masterSecret: someMasterSecret,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"malformed master secret": {
|
||||||
|
fh: newFsWithDirectory(),
|
||||||
|
masterSecret: "asdf",
|
||||||
|
pubKey: someSSHPubKey,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"directory does not exist": {
|
||||||
|
fh: newFsNoDirectory(),
|
||||||
|
pubKey: someSSHPubKey,
|
||||||
|
masterSecret: someMasterSecret,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
if tc.pubKey != "" {
|
||||||
|
require.NoError(tc.fh.Write(someSSHPubKeyPath, []byte(tc.pubKey)))
|
||||||
|
}
|
||||||
|
if tc.masterSecret != "" {
|
||||||
|
require.NoError(tc.fh.Write(constants.MasterSecretFilename, []byte(tc.masterSecret)))
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := NewSSHCmd()
|
||||||
|
cmd.SetOut(&bytes.Buffer{})
|
||||||
|
cmd.SetErr(&bytes.Buffer{})
|
||||||
|
cmd.SetIn(&bytes.Buffer{})
|
||||||
|
|
||||||
|
err := writeCertificateForKey(cmd, someSSHPubKeyPath, tc.fh, logger.NewTest(t))
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
cert, err := tc.fh.Read(fmt.Sprintf("%s/ca_cert.pub", constants.TerraformWorkingDir))
|
||||||
|
require.NoError(err)
|
||||||
|
_, _, _, _, err = ssh.ParseAuthorizedKey(cert)
|
||||||
|
require.NoError(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -39,6 +39,7 @@ Commands:
|
|||||||
* [apply](#constellation-iam-upgrade-apply): Apply an upgrade to an IAM profile
|
* [apply](#constellation-iam-upgrade-apply): Apply an upgrade to an IAM profile
|
||||||
* [version](#constellation-version): Display version of this CLI
|
* [version](#constellation-version): Display version of this CLI
|
||||||
* [init](#constellation-init): Initialize the Constellation cluster
|
* [init](#constellation-init): Initialize the Constellation cluster
|
||||||
|
* [ssh](#constellation-ssh): Prepare your cluster for emergency ssh access
|
||||||
|
|
||||||
## constellation config
|
## constellation config
|
||||||
|
|
||||||
@ -842,3 +843,31 @@ constellation init [flags]
|
|||||||
-C, --workspace string path to the Constellation workspace
|
-C, --workspace string path to the Constellation workspace
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## constellation ssh
|
||||||
|
|
||||||
|
Prepare your cluster for emergency ssh access
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
Prepare your cluster for emergency ssh access and sign a given key pair for authorization.
|
||||||
|
|
||||||
|
```
|
||||||
|
constellation ssh [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for ssh
|
||||||
|
--key string the path to an existing ssh public key
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options inherited from parent commands
|
||||||
|
|
||||||
|
```
|
||||||
|
--debug enable debug logging
|
||||||
|
--force disable version compatibility checks - might result in corrupted clusters
|
||||||
|
--tf-log string Terraform log level (default "NONE")
|
||||||
|
-C, --workspace string path to the Constellation workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
@ -42,6 +42,10 @@ const (
|
|||||||
DefaultWorkerGroupName = "worker_default"
|
DefaultWorkerGroupName = "worker_default"
|
||||||
// CLIDebugLogFile is the name of the debug log file for constellation init/constellation apply.
|
// CLIDebugLogFile is the name of the debug log file for constellation init/constellation apply.
|
||||||
CLIDebugLogFile = "constellation-debug.log"
|
CLIDebugLogFile = "constellation-debug.log"
|
||||||
|
// SSHCAKeySuffix is the suffix used together with the DEKPrefix to derive an SSH CA key for emergency ssh access.
|
||||||
|
SSHCAKeySuffix = "ca_emergency_ssh"
|
||||||
|
// SSHCAKeyPath is the path to the emergency SSH CA key on the node.
|
||||||
|
SSHCAKeyPath = "/run/ssh/ssh_ca.pub"
|
||||||
|
|
||||||
//
|
//
|
||||||
// Ports.
|
// Ports.
|
||||||
|
@ -6,7 +6,10 @@ go_library(
|
|||||||
srcs = ["crypto.go"],
|
srcs = ["crypto.go"],
|
||||||
importpath = "github.com/edgelesssys/constellation/v2/internal/crypto",
|
importpath = "github.com/edgelesssys/constellation/v2/internal/crypto",
|
||||||
visibility = ["//:__subpackages__"],
|
visibility = ["//:__subpackages__"],
|
||||||
deps = ["@org_golang_x_crypto//hkdf"],
|
deps = [
|
||||||
|
"@org_golang_x_crypto//hkdf",
|
||||||
|
"@org_golang_x_crypto//ssh",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
|
@ -9,6 +9,7 @@ package crypto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
@ -18,6 +19,7 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -62,6 +64,19 @@ func GenerateRandomBytes(length int) ([]byte, error) {
|
|||||||
return nonce, nil
|
return nonce, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateEmergencySSHCAKey creates a CA that is used to sign keys for emergency ssh access.
|
||||||
|
func GenerateEmergencySSHCAKey(seed []byte) (ssh.Signer, error) {
|
||||||
|
_, priv, err := ed25519.GenerateKey(bytes.NewReader(seed))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ca, err := ssh.NewSignerFromSigner(priv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ca, nil
|
||||||
|
}
|
||||||
|
|
||||||
// PemToX509Cert takes a list of PEM-encoded certificates, parses the first one and returns it
|
// PemToX509Cert takes a list of PEM-encoded certificates, parses the first one and returns it
|
||||||
// as an x.509 certificate.
|
// as an x.509 certificate.
|
||||||
func PemToX509Cert(raw []byte) (*x509.Certificate, error) {
|
func PemToX509Cert(raw []byte) (*x509.Certificate, error) {
|
||||||
|
@ -7,6 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
package crypto
|
package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -121,6 +122,47 @@ func TestGenerateRandomBytes(t *testing.T) {
|
|||||||
assert.Len(n3, 16)
|
assert.Len(n3, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGenerateEmergencySSHCAKey(t *testing.T) {
|
||||||
|
nullKey := make([]byte, ed25519.SeedSize)
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
key []byte
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"key length = 0": {
|
||||||
|
key: make([]byte, 0),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"valid key": {
|
||||||
|
key: nullKey,
|
||||||
|
},
|
||||||
|
"nil input": {
|
||||||
|
key: nil,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"long key": {
|
||||||
|
key: make([]byte, 256),
|
||||||
|
},
|
||||||
|
"key too short": {
|
||||||
|
key: make([]byte, ed25519.SeedSize-1),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
_, err := GenerateEmergencySSHCAKey(tc.key)
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPemToX509Cert(t *testing.T) {
|
func TestPemToX509Cert(t *testing.T) {
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
pemCert []byte
|
pemCert []byte
|
||||||
|
@ -19,6 +19,7 @@ go_library(
|
|||||||
"@org_golang_google_grpc//codes",
|
"@org_golang_google_grpc//codes",
|
||||||
"@org_golang_google_grpc//credentials",
|
"@org_golang_google_grpc//credentials",
|
||||||
"@org_golang_google_grpc//status",
|
"@org_golang_google_grpc//status",
|
||||||
|
"@org_golang_x_crypto//ssh",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ go_test(
|
|||||||
embed = [":server"],
|
embed = [":server"],
|
||||||
deps = [
|
deps = [
|
||||||
"//internal/attestation",
|
"//internal/attestation",
|
||||||
|
"//internal/constants",
|
||||||
"//internal/logger",
|
"//internal/logger",
|
||||||
"//internal/versions/components",
|
"//internal/versions/components",
|
||||||
"//joinservice/joinproto",
|
"//joinservice/joinproto",
|
||||||
|
@ -9,6 +9,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ed25519"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
@ -21,6 +22,7 @@ import (
|
|||||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/versions/components"
|
"github.com/edgelesssys/constellation/v2/internal/versions/components"
|
||||||
"github.com/edgelesssys/constellation/v2/joinservice/joinproto"
|
"github.com/edgelesssys/constellation/v2/joinservice/joinproto"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
@ -100,6 +102,18 @@ func (s *Server) IssueJoinTicket(ctx context.Context, req *joinproto.IssueJoinTi
|
|||||||
return nil, status.Errorf(codes.Internal, "getting key for stateful disk: %s", err)
|
return nil, status.Errorf(codes.Internal, "getting key for stateful disk: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info("Requesting emergency SSH CA derivation key")
|
||||||
|
sshCAKeySeed, err := s.dataKeyGetter.GetDataKey(ctx, constants.SSHCAKeySuffix, ed25519.SeedSize)
|
||||||
|
if err != nil {
|
||||||
|
log.With(slog.Any("error", err)).Error("Failed to get seed material to derive SSH CA key")
|
||||||
|
return nil, status.Errorf(codes.Internal, "getting emergency SSH CA seed material: %s", err)
|
||||||
|
}
|
||||||
|
ca, err := crypto.GenerateEmergencySSHCAKey(sshCAKeySeed)
|
||||||
|
if err != nil {
|
||||||
|
log.With(slog.Any("error", err)).Error("Failed to derive ssh CA key from seed material")
|
||||||
|
return nil, status.Errorf(codes.Internal, "generating ssh emergency CA key: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
log.Info("Creating Kubernetes join token")
|
log.Info("Creating Kubernetes join token")
|
||||||
kubeArgs, err := s.joinTokenGetter.GetJoinToken(constants.KubernetesJoinTokenTTL)
|
kubeArgs, err := s.joinTokenGetter.GetJoinToken(constants.KubernetesJoinTokenTTL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -167,6 +181,7 @@ func (s *Server) IssueJoinTicket(ctx context.Context, req *joinproto.IssueJoinTi
|
|||||||
KubeletCert: kubeletCert,
|
KubeletCert: kubeletCert,
|
||||||
ControlPlaneFiles: controlPlaneFiles,
|
ControlPlaneFiles: controlPlaneFiles,
|
||||||
KubernetesComponents: components,
|
KubernetesComponents: components,
|
||||||
|
AuthorizedCaPublicKey: ssh.MarshalAuthorizedKey(ca.PublicKey()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,11 +8,13 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ed25519"
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/versions/components"
|
"github.com/edgelesssys/constellation/v2/internal/versions/components"
|
||||||
"github.com/edgelesssys/constellation/v2/joinservice/joinproto"
|
"github.com/edgelesssys/constellation/v2/joinservice/joinproto"
|
||||||
@ -29,6 +31,7 @@ func TestMain(m *testing.M) {
|
|||||||
func TestIssueJoinTicket(t *testing.T) {
|
func TestIssueJoinTicket(t *testing.T) {
|
||||||
someErr := errors.New("error")
|
someErr := errors.New("error")
|
||||||
testKey := []byte{0x1, 0x2, 0x3}
|
testKey := []byte{0x1, 0x2, 0x3}
|
||||||
|
testCaKey := make([]byte, ed25519.SeedSize)
|
||||||
testCert := []byte{0x4, 0x5, 0x6}
|
testCert := []byte{0x4, 0x5, 0x6}
|
||||||
measurementSecret := []byte{0x7, 0x8, 0x9}
|
measurementSecret := []byte{0x7, 0x8, 0x9}
|
||||||
uuid := "uuid"
|
uuid := "uuid"
|
||||||
@ -62,6 +65,7 @@ func TestIssueJoinTicket(t *testing.T) {
|
|||||||
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
||||||
uuid: testKey,
|
uuid: testKey,
|
||||||
attestation.MeasurementSecretContext: measurementSecret,
|
attestation.MeasurementSecretContext: measurementSecret,
|
||||||
|
constants.SSHCAKeySuffix: testCaKey,
|
||||||
}},
|
}},
|
||||||
ca: stubCA{cert: testCert, nodeName: "node"},
|
ca: stubCA{cert: testCert, nodeName: "node"},
|
||||||
kubeClient: stubKubeClient{getComponentsVal: clusterComponents, getK8sComponentsRefFromNodeVersionCRDVal: "k8s-components-ref"},
|
kubeClient: stubKubeClient{getComponentsVal: clusterComponents, getK8sComponentsRefFromNodeVersionCRDVal: "k8s-components-ref"},
|
||||||
@ -71,6 +75,7 @@ func TestIssueJoinTicket(t *testing.T) {
|
|||||||
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
||||||
uuid: testKey,
|
uuid: testKey,
|
||||||
attestation.MeasurementSecretContext: measurementSecret,
|
attestation.MeasurementSecretContext: measurementSecret,
|
||||||
|
constants.SSHCAKeySuffix: testCaKey,
|
||||||
}},
|
}},
|
||||||
ca: stubCA{cert: testCert, nodeName: "node"},
|
ca: stubCA{cert: testCert, nodeName: "node"},
|
||||||
kubeClient: stubKubeClient{getComponentsErr: someErr},
|
kubeClient: stubKubeClient{getComponentsErr: someErr},
|
||||||
@ -81,6 +86,7 @@ func TestIssueJoinTicket(t *testing.T) {
|
|||||||
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
||||||
uuid: testKey,
|
uuid: testKey,
|
||||||
attestation.MeasurementSecretContext: measurementSecret,
|
attestation.MeasurementSecretContext: measurementSecret,
|
||||||
|
constants.SSHCAKeySuffix: testCaKey,
|
||||||
}},
|
}},
|
||||||
ca: stubCA{cert: testCert, nodeName: "node", getNameErr: someErr},
|
ca: stubCA{cert: testCert, nodeName: "node", getNameErr: someErr},
|
||||||
kubeClient: stubKubeClient{getComponentsVal: clusterComponents, getK8sComponentsRefFromNodeVersionCRDVal: "k8s-components-ref"},
|
kubeClient: stubKubeClient{getComponentsVal: clusterComponents, getK8sComponentsRefFromNodeVersionCRDVal: "k8s-components-ref"},
|
||||||
@ -91,6 +97,7 @@ func TestIssueJoinTicket(t *testing.T) {
|
|||||||
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
||||||
uuid: testKey,
|
uuid: testKey,
|
||||||
attestation.MeasurementSecretContext: measurementSecret,
|
attestation.MeasurementSecretContext: measurementSecret,
|
||||||
|
constants.SSHCAKeySuffix: testCaKey,
|
||||||
}},
|
}},
|
||||||
ca: stubCA{cert: testCert, nodeName: "node"},
|
ca: stubCA{cert: testCert, nodeName: "node"},
|
||||||
kubeClient: stubKubeClient{getComponentsVal: clusterComponents, addNodeToJoiningNodesErr: someErr, getK8sComponentsRefFromNodeVersionCRDVal: "k8s-components-ref"},
|
kubeClient: stubKubeClient{getComponentsVal: clusterComponents, addNodeToJoiningNodesErr: someErr, getK8sComponentsRefFromNodeVersionCRDVal: "k8s-components-ref"},
|
||||||
@ -108,6 +115,7 @@ func TestIssueJoinTicket(t *testing.T) {
|
|||||||
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
||||||
uuid: testKey,
|
uuid: testKey,
|
||||||
attestation.MeasurementSecretContext: measurementSecret,
|
attestation.MeasurementSecretContext: measurementSecret,
|
||||||
|
constants.SSHCAKeySuffix: testCaKey,
|
||||||
}},
|
}},
|
||||||
ca: stubCA{cert: testCert, nodeName: "node"},
|
ca: stubCA{cert: testCert, nodeName: "node"},
|
||||||
kubeClient: stubKubeClient{getComponentsVal: clusterComponents, getK8sComponentsRefFromNodeVersionCRDVal: "k8s-components-ref"},
|
kubeClient: stubKubeClient{getComponentsVal: clusterComponents, getK8sComponentsRefFromNodeVersionCRDVal: "k8s-components-ref"},
|
||||||
@ -118,6 +126,7 @@ func TestIssueJoinTicket(t *testing.T) {
|
|||||||
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
||||||
uuid: testKey,
|
uuid: testKey,
|
||||||
attestation.MeasurementSecretContext: measurementSecret,
|
attestation.MeasurementSecretContext: measurementSecret,
|
||||||
|
constants.SSHCAKeySuffix: testCaKey,
|
||||||
}},
|
}},
|
||||||
ca: stubCA{getCertErr: someErr, nodeName: "node"},
|
ca: stubCA{getCertErr: someErr, nodeName: "node"},
|
||||||
kubeClient: stubKubeClient{getComponentsVal: clusterComponents, getK8sComponentsRefFromNodeVersionCRDVal: "k8s-components-ref"},
|
kubeClient: stubKubeClient{getComponentsVal: clusterComponents, getK8sComponentsRefFromNodeVersionCRDVal: "k8s-components-ref"},
|
||||||
@ -132,6 +141,7 @@ func TestIssueJoinTicket(t *testing.T) {
|
|||||||
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
||||||
uuid: testKey,
|
uuid: testKey,
|
||||||
attestation.MeasurementSecretContext: measurementSecret,
|
attestation.MeasurementSecretContext: measurementSecret,
|
||||||
|
constants.SSHCAKeySuffix: testCaKey,
|
||||||
}},
|
}},
|
||||||
ca: stubCA{cert: testCert, nodeName: "node"},
|
ca: stubCA{cert: testCert, nodeName: "node"},
|
||||||
kubeClient: stubKubeClient{getComponentsVal: clusterComponents, getK8sComponentsRefFromNodeVersionCRDVal: "k8s-components-ref"},
|
kubeClient: stubKubeClient{getComponentsVal: clusterComponents, getK8sComponentsRefFromNodeVersionCRDVal: "k8s-components-ref"},
|
||||||
@ -139,6 +149,28 @@ func TestIssueJoinTicket(t *testing.T) {
|
|||||||
"GetControlPlaneCertificateKey fails": {
|
"GetControlPlaneCertificateKey fails": {
|
||||||
isControlPlane: true,
|
isControlPlane: true,
|
||||||
kubeadm: stubTokenGetter{token: testJoinToken, certificateKeyErr: someErr},
|
kubeadm: stubTokenGetter{token: testJoinToken, certificateKeyErr: someErr},
|
||||||
|
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
||||||
|
uuid: testKey,
|
||||||
|
attestation.MeasurementSecretContext: measurementSecret,
|
||||||
|
constants.SSHCAKeySuffix: testCaKey,
|
||||||
|
}},
|
||||||
|
ca: stubCA{cert: testCert, nodeName: "node"},
|
||||||
|
kubeClient: stubKubeClient{getComponentsVal: clusterComponents, getK8sComponentsRefFromNodeVersionCRDVal: "k8s-components-ref"},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"CA data key to short": {
|
||||||
|
kubeadm: stubTokenGetter{token: testJoinToken},
|
||||||
|
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
||||||
|
uuid: testKey,
|
||||||
|
attestation.MeasurementSecretContext: measurementSecret,
|
||||||
|
constants.SSHCAKeySuffix: testKey,
|
||||||
|
}},
|
||||||
|
ca: stubCA{cert: testCert, nodeName: "node"},
|
||||||
|
kubeClient: stubKubeClient{getComponentsVal: clusterComponents, getK8sComponentsRefFromNodeVersionCRDVal: "k8s-components-ref"},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"CA data key doesn't exist": {
|
||||||
|
kubeadm: stubTokenGetter{token: testJoinToken},
|
||||||
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
kms: stubKeyGetter{dataKeys: map[string][]byte{
|
||||||
uuid: testKey,
|
uuid: testKey,
|
||||||
attestation.MeasurementSecretContext: measurementSecret,
|
attestation.MeasurementSecretContext: measurementSecret,
|
||||||
|
@ -98,6 +98,7 @@ type IssueJoinTicketResponse struct {
|
|||||||
ControlPlaneFiles []*ControlPlaneCertOrKey `protobuf:"bytes,8,rep,name=control_plane_files,json=controlPlaneFiles,proto3" json:"control_plane_files,omitempty"`
|
ControlPlaneFiles []*ControlPlaneCertOrKey `protobuf:"bytes,8,rep,name=control_plane_files,json=controlPlaneFiles,proto3" json:"control_plane_files,omitempty"`
|
||||||
KubernetesVersion string `protobuf:"bytes,9,opt,name=kubernetes_version,json=kubernetesVersion,proto3" json:"kubernetes_version,omitempty"`
|
KubernetesVersion string `protobuf:"bytes,9,opt,name=kubernetes_version,json=kubernetesVersion,proto3" json:"kubernetes_version,omitempty"`
|
||||||
KubernetesComponents []*components.Component `protobuf:"bytes,10,rep,name=kubernetes_components,json=kubernetesComponents,proto3" json:"kubernetes_components,omitempty"`
|
KubernetesComponents []*components.Component `protobuf:"bytes,10,rep,name=kubernetes_components,json=kubernetesComponents,proto3" json:"kubernetes_components,omitempty"`
|
||||||
|
AuthorizedCaPublicKey []byte `protobuf:"bytes,11,opt,name=authorized_ca_public_key,json=authorizedCaPublicKey,proto3" json:"authorized_ca_public_key,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@ -202,6 +203,13 @@ func (x *IssueJoinTicketResponse) GetKubernetesComponents() []*components.Compon
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *IssueJoinTicketResponse) GetAuthorizedCaPublicKey() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.AuthorizedCaPublicKey
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type ControlPlaneCertOrKey struct {
|
type ControlPlaneCertOrKey struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
@ -367,7 +375,7 @@ var file_joinservice_joinproto_join_proto_rawDesc = string([]byte{
|
|||||||
0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||||
0x12, 0x28, 0x0a, 0x10, 0x69, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x70,
|
0x12, 0x28, 0x0a, 0x10, 0x69, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x70,
|
||||||
0x6c, 0x61, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x73, 0x43, 0x6f,
|
0x6c, 0x61, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x73, 0x43, 0x6f,
|
||||||
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x22, 0x8e, 0x04, 0x0a, 0x17, 0x49,
|
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x22, 0xc7, 0x04, 0x0a, 0x17, 0x49,
|
||||||
0x73, 0x73, 0x75, 0x65, 0x4a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65,
|
0x73, 0x73, 0x75, 0x65, 0x4a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65,
|
||||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f,
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f,
|
||||||
0x64, 0x69, 0x73, 0x6b, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c,
|
0x64, 0x69, 0x73, 0x6b, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c,
|
||||||
@ -400,38 +408,41 @@ var file_joinservice_joinproto_join_proto_rawDesc = string([]byte{
|
|||||||
0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15,
|
0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15,
|
||||||
0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x70,
|
0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x43, 0x6f, 0x6d, 0x70,
|
||||||
0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x52, 0x14, 0x6b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65,
|
0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x52, 0x14, 0x6b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65,
|
||||||
0x73, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x43, 0x0a, 0x19, 0x63,
|
0x73, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x37, 0x0a, 0x18, 0x61,
|
||||||
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5f, 0x63, 0x65, 0x72,
|
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x63, 0x61, 0x5f, 0x70, 0x75, 0x62,
|
||||||
0x74, 0x5f, 0x6f, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
|
0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x61,
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04,
|
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x43, 0x61, 0x50, 0x75, 0x62, 0x6c, 0x69,
|
||||||
0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61,
|
0x63, 0x4b, 0x65, 0x79, 0x22, 0x43, 0x0a, 0x19, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f,
|
||||||
0x22, 0x37, 0x0a, 0x18, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x6a, 0x6f, 0x69, 0x6e, 0x54,
|
0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x5f, 0x6b, 0x65,
|
||||||
0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09,
|
0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x64, 0x69, 0x73, 0x6b, 0x5f, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20,
|
||||||
0x08, 0x64, 0x69, 0x73, 0x6b, 0x55, 0x75, 0x69, 0x64, 0x22, 0x70, 0x0a, 0x19, 0x49, 0x73, 0x73,
|
0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x37, 0x0a, 0x18, 0x49, 0x73, 0x73,
|
||||||
0x75, 0x65, 0x52, 0x65, 0x6a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65,
|
0x75, 0x65, 0x52, 0x65, 0x6a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65,
|
||||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f,
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x69, 0x73, 0x6b, 0x5f, 0x75, 0x75,
|
||||||
0x64, 0x69, 0x73, 0x6b, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c,
|
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x69, 0x73, 0x6b, 0x55, 0x75,
|
||||||
0x73, 0x74, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x6b, 0x4b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x12,
|
0x69, 0x64, 0x22, 0x70, 0x0a, 0x19, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x6a, 0x6f, 0x69,
|
||||||
0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72,
|
0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||||
0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72,
|
0x24, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x69, 0x73, 0x6b, 0x5f, 0x6b, 0x65,
|
||||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x32, 0xab, 0x01, 0x0a, 0x03,
|
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x65, 0x44, 0x69,
|
||||||
0x41, 0x50, 0x49, 0x12, 0x4e, 0x0a, 0x0f, 0x49, 0x73, 0x73, 0x75, 0x65, 0x4a, 0x6f, 0x69, 0x6e,
|
0x73, 0x6b, 0x4b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x12, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65,
|
||||||
0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x1c, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x2e, 0x49, 0x73,
|
0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||||
0x73, 0x75, 0x65, 0x4a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71,
|
0x0c, 0x52, 0x11, 0x6d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65,
|
||||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x2e, 0x49, 0x73, 0x73, 0x75,
|
0x63, 0x72, 0x65, 0x74, 0x32, 0xab, 0x01, 0x0a, 0x03, 0x41, 0x50, 0x49, 0x12, 0x4e, 0x0a, 0x0f,
|
||||||
0x65, 0x4a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
0x49, 0x73, 0x73, 0x75, 0x65, 0x4a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12,
|
||||||
0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x11, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x6a, 0x6f,
|
0x1c, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x4a, 0x6f, 0x69, 0x6e,
|
||||||
0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x1e, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x2e,
|
0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e,
|
||||||
|
0x6a, 0x6f, 0x69, 0x6e, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x4a, 0x6f, 0x69, 0x6e, 0x54, 0x69,
|
||||||
|
0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x11,
|
||||||
0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x6a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65,
|
0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x6a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65,
|
||||||
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x2e,
|
0x74, 0x12, 0x1e, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65,
|
||||||
0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65, 0x6a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65,
|
0x6a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74,
|
0x74, 0x1a, 0x1f, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x65,
|
||||||
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x64, 0x67, 0x65, 0x6c, 0x65, 0x73, 0x73,
|
0x6a, 0x6f, 0x69, 0x6e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
0x73, 0x79, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f,
|
0x73, 0x65, 0x42, 0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||||
0x6e, 0x2f, 0x76, 0x32, 0x2f, 0x6a, 0x6f, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
0x2f, 0x65, 0x64, 0x67, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x73, 0x79, 0x73, 0x2f, 0x63, 0x6f, 0x6e,
|
||||||
0x2f, 0x6a, 0x6f, 0x69, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
0x73, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x32, 0x2f, 0x6a, 0x6f,
|
||||||
0x6f, 0x33,
|
0x69, 0x6e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x6a, 0x6f, 0x69, 0x6e, 0x70, 0x72,
|
||||||
|
0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
})
|
})
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -45,6 +45,8 @@ message IssueJoinTicketResponse {
|
|||||||
string kubernetes_version = 9;
|
string kubernetes_version = 9;
|
||||||
// kubernetes_components is a list of components to install on the node.
|
// kubernetes_components is a list of components to install on the node.
|
||||||
repeated components.Component kubernetes_components = 10;
|
repeated components.Component kubernetes_components = 10;
|
||||||
|
// authorized_ca_public_key is an ssh ca key that can be used to connect to a node in case of an emergency.
|
||||||
|
bytes authorized_ca_public_key = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
message control_plane_cert_or_key {
|
message control_plane_cert_or_key {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user