feat: use SSH host certificates (#3786)

This commit is contained in:
miampf 2025-07-01 12:47:04 +02:00 committed by GitHub
parent 95f17a6d06
commit 7ea5c41f9b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 706 additions and 117 deletions

View file

@ -15,12 +15,15 @@ import (
"github.com/edgelesssys/constellation/v2/internal/attestation"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/versions/components"
"github.com/edgelesssys/constellation/v2/joinservice/joinproto"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
"golang.org/x/crypto/ssh"
kubeadmv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
)
@ -36,6 +39,11 @@ func TestIssueJoinTicket(t *testing.T) {
measurementSecret := []byte{0x7, 0x8, 0x9}
uuid := "uuid"
pubkey, _, err := ed25519.GenerateKey(nil)
require.NoError(t, err)
hostSSHPubKey, err := ssh.NewPublicKey(pubkey)
require.NoError(t, err)
testJoinToken := &kubeadmv1.BootstrapTokenDiscovery{
APIServerEndpoint: "192.0.2.1",
CACertHashes: []string{"hash"},
@ -52,13 +60,15 @@ func TestIssueJoinTicket(t *testing.T) {
}
testCases := map[string]struct {
isControlPlane bool
kubeadm stubTokenGetter
kms stubKeyGetter
ca stubCA
kubeClient stubKubeClient
missingComponentsReferenceFile bool
wantErr bool
isControlPlane bool
kubeadm stubTokenGetter
kms stubKeyGetter
ca stubCA
kubeClient stubKubeClient
missingComponentsReferenceFile bool
missingAdditionalPrincipalsFile bool
missingSSHHostKey bool
wantErr bool
}{
"worker node": {
kubeadm: stubTokenGetter{token: testJoinToken},
@ -179,6 +189,30 @@ func TestIssueJoinTicket(t *testing.T) {
kubeClient: stubKubeClient{getComponentsVal: clusterComponents, getK8sComponentsRefFromNodeVersionCRDVal: "k8s-components-ref"},
wantErr: true,
},
"Additional principals file is missing": {
kubeadm: stubTokenGetter{token: testJoinToken},
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"},
missingAdditionalPrincipalsFile: true,
wantErr: true,
},
"Host pubkey is missing": {
kubeadm: stubTokenGetter{token: testJoinToken},
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"},
missingSSHHostKey: true,
wantErr: true,
},
}
for name, tc := range testCases {
@ -188,6 +222,11 @@ func TestIssueJoinTicket(t *testing.T) {
salt := []byte{0xA, 0xB, 0xC}
fh := file.NewHandler(afero.NewMemMapFs())
if !tc.missingAdditionalPrincipalsFile {
require.NoError(fh.Write(constants.SSHAdditionalPrincipalsPath, []byte("*"), file.OptMkdirAll))
}
api := Server{
measurementSalt: salt,
ca: tc.ca,
@ -195,11 +234,20 @@ func TestIssueJoinTicket(t *testing.T) {
dataKeyGetter: tc.kms,
kubeClient: &tc.kubeClient,
log: logger.NewTest(t),
fileHandler: fh,
}
var keyToSend []byte
if tc.missingSSHHostKey {
keyToSend = nil
} else {
keyToSend = hostSSHPubKey.Marshal()
}
req := &joinproto.IssueJoinTicketRequest{
DiskUuid: "uuid",
IsControlPlane: tc.isControlPlane,
HostPublicKey: keyToSend,
}
resp, err := api.IssueJoinTicket(t.Context(), req)
if tc.wantErr {
@ -260,6 +308,7 @@ func TestIssueRejoinTicker(t *testing.T) {
joinTokenGetter: stubTokenGetter{},
dataKeyGetter: tc.keyGetter,
log: logger.NewTest(t),
fileHandler: file.NewHandler(afero.NewMemMapFs()),
}
req := &joinproto.IssueRejoinTicketRequest{