2022-05-23 05:36:54 -04:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
2022-06-15 10:00:48 -04:00
|
|
|
"path/filepath"
|
2022-05-23 05:36:54 -04:00
|
|
|
"time"
|
|
|
|
|
2022-06-15 10:00:48 -04:00
|
|
|
attestationtypes "github.com/edgelesssys/constellation/internal/attestation/types"
|
2022-05-23 05:36:54 -04:00
|
|
|
"github.com/edgelesssys/constellation/internal/constants"
|
|
|
|
"github.com/edgelesssys/constellation/internal/file"
|
2022-06-28 10:51:30 -04:00
|
|
|
"github.com/edgelesssys/constellation/internal/grpc/grpclog"
|
|
|
|
"github.com/edgelesssys/constellation/internal/logger"
|
2022-07-05 05:41:31 -04:00
|
|
|
"github.com/edgelesssys/constellation/joinservice/joinproto"
|
2022-06-28 10:51:30 -04:00
|
|
|
"go.uber.org/zap"
|
2022-05-23 05:36:54 -04:00
|
|
|
"google.golang.org/grpc"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"google.golang.org/grpc/credentials"
|
|
|
|
"google.golang.org/grpc/status"
|
|
|
|
|
|
|
|
kubeadmv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Server implements the core logic of Constellation's node activation service.
|
|
|
|
type Server struct {
|
2022-06-28 10:51:30 -04:00
|
|
|
log *logger.Logger
|
2022-05-23 05:36:54 -04:00
|
|
|
file file.Handler
|
|
|
|
joinTokenGetter joinTokenGetter
|
|
|
|
dataKeyGetter dataKeyGetter
|
2022-06-02 09:58:19 -04:00
|
|
|
ca certificateAuthority
|
2022-07-05 05:41:31 -04:00
|
|
|
joinproto.UnimplementedAPIServer
|
2022-05-23 05:36:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// New initializes a new Server.
|
2022-06-28 10:51:30 -04:00
|
|
|
func New(log *logger.Logger, fileHandler file.Handler, ca certificateAuthority, joinTokenGetter joinTokenGetter, dataKeyGetter dataKeyGetter) *Server {
|
2022-05-23 05:36:54 -04:00
|
|
|
return &Server{
|
2022-06-28 10:51:30 -04:00
|
|
|
log: log,
|
2022-05-23 05:36:54 -04:00
|
|
|
file: fileHandler,
|
|
|
|
joinTokenGetter: joinTokenGetter,
|
|
|
|
dataKeyGetter: dataKeyGetter,
|
2022-06-02 09:58:19 -04:00
|
|
|
ca: ca,
|
2022-05-23 05:36:54 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run starts the gRPC server on the given port, using the provided tlsConfig.
|
2022-06-13 05:40:27 -04:00
|
|
|
func (s *Server) Run(creds credentials.TransportCredentials, port string) error {
|
2022-06-28 10:51:30 -04:00
|
|
|
s.log.WithIncreasedLevel(zap.WarnLevel).Named("gRPC").ReplaceGRPCLogger()
|
2022-05-23 05:36:54 -04:00
|
|
|
grpcServer := grpc.NewServer(
|
2022-06-13 05:40:27 -04:00
|
|
|
grpc.Creds(creds),
|
2022-06-28 10:51:30 -04:00
|
|
|
s.log.Named("gRPC").GetServerUnaryInterceptor(),
|
2022-05-23 05:36:54 -04:00
|
|
|
)
|
|
|
|
|
2022-07-05 05:41:31 -04:00
|
|
|
joinproto.RegisterAPIServer(grpcServer, s)
|
2022-05-23 05:36:54 -04:00
|
|
|
|
|
|
|
lis, err := net.Listen("tcp", net.JoinHostPort("", port))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to listen: %s", err)
|
|
|
|
}
|
2022-06-28 10:51:30 -04:00
|
|
|
s.log.Infof("Starting activation service on %s", lis.Addr().String())
|
2022-05-23 05:36:54 -04:00
|
|
|
return grpcServer.Serve(lis)
|
|
|
|
}
|
|
|
|
|
2022-07-05 05:41:31 -04:00
|
|
|
// IssueJoinTicket handles activation requests of Constellation nodes.
|
|
|
|
// A node will receive:
|
2022-06-21 05:10:32 -04:00
|
|
|
// - stateful disk encryption key.
|
|
|
|
// - Kubernetes join token.
|
|
|
|
// - cluster and owner ID to taint the node as initialized.
|
2022-07-05 05:41:31 -04:00
|
|
|
// In addition, control plane nodes receive:
|
2022-06-21 05:10:32 -04:00
|
|
|
// - a decryption key for CA certificates uploaded to the Kubernetes cluster.
|
2022-07-05 05:41:31 -04:00
|
|
|
func (s *Server) IssueJoinTicket(ctx context.Context, req *joinproto.IssueJoinTicketRequest) (resp *joinproto.IssueJoinTicketResponse, retErr error) {
|
|
|
|
s.log.Infof("IssueJoinTicket called")
|
2022-06-21 05:10:32 -04:00
|
|
|
|
2022-07-05 05:41:31 -04:00
|
|
|
defer func() {
|
|
|
|
if retErr != nil {
|
|
|
|
s.log.Errorf("IssueJoinTicket failed: %s", retErr)
|
|
|
|
retErr = fmt.Errorf("IssueJoinTicket failed: %w", retErr)
|
|
|
|
}
|
|
|
|
}()
|
2022-06-21 05:10:32 -04:00
|
|
|
|
2022-06-28 10:51:30 -04:00
|
|
|
log := s.log.With(zap.String("peerAddress", grpclog.PeerAddrFromContext(ctx)))
|
|
|
|
log.Infof("Loading IDs")
|
2022-06-15 10:00:48 -04:00
|
|
|
var id attestationtypes.ID
|
2022-06-29 10:13:01 -04:00
|
|
|
if err := s.file.ReadJSON(filepath.Join(constants.ServiceBasePath, constants.IDFilename), &id); err != nil {
|
2022-06-28 10:51:30 -04:00
|
|
|
log.With(zap.Error(err)).Errorf("Unable to load IDs")
|
2022-07-05 05:41:31 -04:00
|
|
|
return nil, status.Errorf(codes.Internal, "unable to load IDs: %s", err)
|
2022-05-23 05:36:54 -04:00
|
|
|
}
|
|
|
|
|
2022-06-28 10:51:30 -04:00
|
|
|
log.Infof("Requesting disk encryption key")
|
2022-07-05 05:41:31 -04:00
|
|
|
stateDiskKey, err := s.dataKeyGetter.GetDataKey(ctx, req.DiskUuid, constants.StateDiskKeyLength)
|
2022-05-23 05:36:54 -04:00
|
|
|
if err != nil {
|
2022-06-28 10:51:30 -04:00
|
|
|
log.With(zap.Error(err)).Errorf("Unable to get key for stateful disk")
|
2022-07-05 05:41:31 -04:00
|
|
|
return nil, status.Errorf(codes.Internal, "unable to get key for stateful disk: %s", err)
|
2022-05-23 05:36:54 -04:00
|
|
|
}
|
|
|
|
|
2022-06-28 10:51:30 -04:00
|
|
|
log.Infof("Creating Kubernetes join token")
|
2022-05-23 05:36:54 -04:00
|
|
|
kubeArgs, err := s.joinTokenGetter.GetJoinToken(constants.KubernetesJoinTokenTTL)
|
|
|
|
if err != nil {
|
2022-06-28 10:51:30 -04:00
|
|
|
log.With(zap.Error(err)).Errorf("Unable to generate Kubernetes join arguments")
|
2022-07-05 05:41:31 -04:00
|
|
|
return nil, status.Errorf(codes.Internal, "unable to generate Kubernetes join arguments: %s", err)
|
2022-05-23 05:36:54 -04:00
|
|
|
}
|
|
|
|
|
2022-06-28 10:51:30 -04:00
|
|
|
log.Infof("Creating signed kubelet certificate")
|
2022-07-05 05:41:31 -04:00
|
|
|
kubeletCert, kubeletKey, err := s.ca.GetCertificate(req.NodeName)
|
2022-06-02 09:58:19 -04:00
|
|
|
if err != nil {
|
2022-07-05 05:41:31 -04:00
|
|
|
return nil, status.Errorf(codes.Internal, "unable to generate kubelet certificate: %s", err)
|
2022-06-02 09:58:19 -04:00
|
|
|
}
|
|
|
|
|
2022-07-05 05:41:31 -04:00
|
|
|
var certKey string
|
|
|
|
if req.IsControlPlane {
|
|
|
|
log.Infof("Creating control plane certificate key")
|
|
|
|
certKey, err = s.joinTokenGetter.GetControlPlaneCertificateKey()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("ActivateControlPlane failed: %w", err)
|
|
|
|
}
|
|
|
|
}
|
2022-05-23 05:36:54 -04:00
|
|
|
|
2022-07-05 05:41:31 -04:00
|
|
|
s.log.Infof("IssueJoinTicket successful")
|
|
|
|
return &joinproto.IssueJoinTicketResponse{
|
|
|
|
StateDiskKey: stateDiskKey,
|
|
|
|
ClusterId: id.Cluster,
|
|
|
|
OwnerId: id.Owner,
|
|
|
|
ApiServerEndpoint: kubeArgs.APIServerEndpoint,
|
|
|
|
Token: kubeArgs.Token,
|
|
|
|
DiscoveryTokenCaCertHash: kubeArgs.CACertHashes[0],
|
|
|
|
KubeletCert: kubeletCert,
|
|
|
|
KubeletKey: kubeletKey,
|
|
|
|
CertificateKey: certKey,
|
|
|
|
}, nil
|
2022-05-23 05:36:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// joinTokenGetter returns Kubernetes bootstrap (join) tokens.
|
|
|
|
type joinTokenGetter interface {
|
|
|
|
// GetJoinToken returns a bootstrap (join) token.
|
|
|
|
GetJoinToken(ttl time.Duration) (*kubeadmv1.BootstrapTokenDiscovery, error)
|
2022-06-21 05:10:32 -04:00
|
|
|
GetControlPlaneCertificateKey() (string, error)
|
2022-05-23 05:36:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// dataKeyGetter interacts with Constellation's key management system to retrieve keys.
|
|
|
|
type dataKeyGetter interface {
|
|
|
|
// GetDataKey returns a key derived from Constellation's KMS.
|
|
|
|
GetDataKey(ctx context.Context, uuid string, length int) ([]byte, error)
|
|
|
|
}
|
|
|
|
|
2022-06-02 09:58:19 -04:00
|
|
|
type certificateAuthority interface {
|
|
|
|
// GetCertificate returns a certificate and private key, signed by the issuer.
|
|
|
|
GetCertificate(nodeName string) (kubeletCert []byte, kubeletKey []byte, err error)
|
|
|
|
}
|