diff --git a/grader/gradeapi/gradeapi.go b/grader/gradeapi/gradeapi.go index aa52adc..e8b6fcb 100644 --- a/grader/gradeapi/gradeapi.go +++ b/grader/gradeapi/gradeapi.go @@ -8,11 +8,12 @@ import ( "context" "fmt" "net" - "os" - "path" "github.com/benschlueter/delegatio/grader/gradeapi/gradeproto" "github.com/benschlueter/delegatio/internal/config" + "github.com/benschlueter/delegatio/internal/k8sapi" + "github.com/benschlueter/delegatio/internal/store" + "github.com/benschlueter/delegatio/internal/storewrapper" "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -20,16 +21,34 @@ import ( // API is the API. type API struct { - logger *zap.Logger - dialer Dialer + logger *zap.Logger + dialer Dialer + client *k8sapi.Client + backingStore store.Store + gradeproto.UnimplementedAPIServer } // New creates a new API. -func New(logger *zap.Logger, dialer Dialer) (*API, error) { +func New(logger *zap.Logger, dialer Dialer, initStore bool) (*API, error) { + // use the current context in kubeconfig + client, err := k8sapi.NewClient(logger) + if err != nil { + return nil, err + } + var store store.Store + if initStore { + store, err = client.GetStore() + if err != nil { + return nil, err + } + } + return &API{ - logger: logger, - dialer: dialer, + logger: logger, + dialer: dialer, + client: client, + backingStore: store, }, nil } @@ -52,44 +71,14 @@ func (a *API) grpcWithDialer() grpc.DialOption { }) } -func (a *API) readFile(fileName string) ([]byte, error) { - file, err := os.Open(fileName) - if err != nil { - return nil, err - } - defer file.Close() - - if _, err := file.Seek(0, 0); err != nil { - return nil, err - } - fileInfo, _ := file.Stat() - fileSize := fileInfo.Size() - bytes := make([]byte, fileSize) - if _, err := file.Read(bytes); err != nil { - return nil, err - } - return bytes, nil +func (a *API) data() storewrapper.StoreWrapper { + return storewrapper.StoreWrapper{Store: a.backingStore} } // SendGradingRequest sends a grading request to the grader service. -func (a *API) SendGradingRequest(ctx context.Context, fileName string) (int, error) { - f, err := os.CreateTemp("/tmp", "gradingRequest-") - if err != nil { - return 0, err - } - defer os.Remove(f.Name()) - defer f.Close() - // against a race condition - if err := f.Sync(); err != nil { - return 0, err - } - - _, nonceName := path.Split(f.Name()) - a.logger.Info("create nonce file", zap.String("file", nonceName)) - - fileBytes, err := a.readFile(fileName) - if err != nil { - a.logger.Error("failed to read file", zap.String("file", fileName), zap.Error(err)) +func (a *API) SendGradingRequest(ctx context.Context, fileBytes []byte, signature []byte, studentID string) (int, error) { + if studentID == "" { + return 0, fmt.Errorf("studentID is empty") } conn, err := a.dialInsecure(ctx, fmt.Sprintf("grader-service.%s.svc.cluster.local:%d", config.GraderNamespaceName, config.GradeAPIport)) @@ -98,9 +87,10 @@ func (a *API) SendGradingRequest(ctx context.Context, fileName string) (int, err } client := gradeproto.NewAPIClient(conn) resp, err := client.RequestGrading(ctx, &gradeproto.RequestGradingRequest{ - Id: 1, - Nonce: nonceName, - Solution: fileBytes, + ExerciseId: 1, + Solution: fileBytes, + Signature: signature, + StudentId: studentID, }) if err != nil { return 0, err diff --git a/grader/gradeapi/gradeproto/gradeapi.pb.go b/grader/gradeapi/gradeproto/gradeapi.pb.go index 709e9a5..396143c 100644 --- a/grader/gradeapi/gradeproto/gradeapi.pb.go +++ b/grader/gradeapi/gradeproto/gradeapi.pb.go @@ -25,10 +25,11 @@ type RequestGradingRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - Nonce string `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"` - Solution []byte `protobuf:"bytes,3,opt,name=solution,proto3" json:"solution,omitempty"` - Submit bool `protobuf:"varint,4,opt,name=submit,proto3" json:"submit,omitempty"` + ExerciseId int32 `protobuf:"varint,1,opt,name=exerciseId,proto3" json:"exerciseId,omitempty"` + StudentId string `protobuf:"bytes,2,opt,name=studentId,proto3" json:"studentId,omitempty"` + Solution []byte `protobuf:"bytes,3,opt,name=solution,proto3" json:"solution,omitempty"` + Signature []byte `protobuf:"bytes,4,opt,name=signature,proto3" json:"signature,omitempty"` + Submit bool `protobuf:"varint,5,opt,name=submit,proto3" json:"submit,omitempty"` } func (x *RequestGradingRequest) Reset() { @@ -63,16 +64,16 @@ func (*RequestGradingRequest) Descriptor() ([]byte, []int) { return file_gradeapi_proto_rawDescGZIP(), []int{0} } -func (x *RequestGradingRequest) GetId() int32 { +func (x *RequestGradingRequest) GetExerciseId() int32 { if x != nil { - return x.Id + return x.ExerciseId } return 0 } -func (x *RequestGradingRequest) GetNonce() string { +func (x *RequestGradingRequest) GetStudentId() string { if x != nil { - return x.Nonce + return x.StudentId } return "" } @@ -84,6 +85,13 @@ func (x *RequestGradingRequest) GetSolution() []byte { return nil } +func (x *RequestGradingRequest) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + func (x *RequestGradingRequest) GetSubmit() bool { if x != nil { return x.Submit @@ -150,29 +158,32 @@ var File_gradeapi_proto protoreflect.FileDescriptor var file_gradeapi_proto_rawDesc = []byte{ 0x0a, 0x0e, 0x67, 0x72, 0x61, 0x64, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x12, 0x08, 0x67, 0x72, 0x61, 0x64, 0x65, 0x61, 0x70, 0x69, 0x22, 0x71, 0x0a, 0x15, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x47, 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x6f, 0x6c, - 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x6f, 0x6c, - 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x22, 0x42, 0x0a, - 0x16, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x47, 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, - 0x10, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6c, 0x6f, - 0x67, 0x32, 0x5a, 0x0a, 0x03, 0x41, 0x50, 0x49, 0x12, 0x53, 0x0a, 0x0e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x47, 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x1f, 0x2e, 0x67, 0x72, 0x61, - 0x64, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x47, 0x72, 0x61, - 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x72, - 0x61, 0x64, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x47, 0x72, - 0x61, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3e, 0x5a, - 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x65, 0x6e, 0x73, - 0x63, 0x68, 0x6c, 0x75, 0x65, 0x74, 0x65, 0x72, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, - 0x69, 0x6f, 0x2f, 0x67, 0x72, 0x61, 0x64, 0x65, 0x72, 0x2f, 0x67, 0x72, 0x61, 0x64, 0x65, 0x41, - 0x50, 0x49, 0x2f, 0x67, 0x72, 0x61, 0x64, 0x65, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x08, 0x67, 0x72, 0x61, 0x64, 0x65, 0x61, 0x70, 0x69, 0x22, 0xa7, 0x01, 0x0a, 0x15, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x47, 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x65, + 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x65, 0x78, 0x65, 0x72, 0x63, 0x69, + 0x73, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x49, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, + 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, + 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x73, 0x75, + 0x62, 0x6d, 0x69, 0x74, 0x22, 0x42, 0x0a, 0x16, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x47, + 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x32, 0x5a, 0x0a, 0x03, 0x41, 0x50, 0x49, 0x12, + 0x53, 0x0a, 0x0e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x47, 0x72, 0x61, 0x64, 0x69, 0x6e, + 0x67, 0x12, 0x1f, 0x2e, 0x67, 0x72, 0x61, 0x64, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x47, 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x72, 0x61, 0x64, 0x65, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x47, 0x72, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3e, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x62, 0x65, 0x6e, 0x73, 0x63, 0x68, 0x6c, 0x75, 0x65, 0x74, 0x65, 0x72, 0x2f, + 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x2f, 0x67, 0x72, 0x61, 0x64, 0x65, 0x72, + 0x2f, 0x67, 0x72, 0x61, 0x64, 0x65, 0x41, 0x50, 0x49, 0x2f, 0x67, 0x72, 0x61, 0x64, 0x65, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/grader/gradeapi/gradeproto/gradeapi.proto b/grader/gradeapi/gradeproto/gradeapi.proto index 8e04ad1..ed4d813 100644 --- a/grader/gradeapi/gradeproto/gradeapi.proto +++ b/grader/gradeapi/gradeproto/gradeapi.proto @@ -10,13 +10,14 @@ service API { } message RequestGradingRequest { - int32 id = 1; - string nonce = 2; + int32 exerciseId = 1; + string studentId = 2; bytes solution = 3; - bool submit = 4; + bytes signature = 4; + bool submit = 5; } message RequestGradingResponse { int32 points = 1; bytes log = 2; -} \ No newline at end of file +} diff --git a/grader/gradeapi/graders/graders.go b/grader/gradeapi/graders/graders.go index 7383c06..633b9a5 100644 --- a/grader/gradeapi/graders/graders.go +++ b/grader/gradeapi/graders/graders.go @@ -23,7 +23,7 @@ import ( // of the graders. Currently we do not need any state. type Graders struct { logger *zap.Logger - studentID string + UUID string singleExecTimeout time.Duration totalExecTimeout time.Duration } @@ -32,7 +32,7 @@ type Graders struct { func NewGraders(zapLogger *zap.Logger, studentID string) (*Graders, error) { c := &Graders{ logger: zapLogger, - studentID: studentID, + UUID: studentID, singleExecTimeout: time.Second, totalExecTimeout: 15 * time.Second, } @@ -69,7 +69,7 @@ func (g *Graders) executeCommand(ctx context.Context, fileName string, arg ...st func (g *Graders) writeFileToDisk(_ context.Context, solution []byte) (*os.File, error) { f, err := os.CreateTemp( filepath.Join(config.SandboxPath, "tmp"), - fmt.Sprintf("solution-%s-%v", g.studentID, "now" /*time.Now().Format(time.RFC822)*/), + fmt.Sprintf("solution-%s-%v", g.UUID, "now" /*time.Now().Format(time.RFC822)*/), ) if err != nil { g.logger.Error("failed to create content file", zap.Error(err)) diff --git a/grader/gradeapi/request.go b/grader/gradeapi/request.go index 6dee7b8..a55ad8f 100644 --- a/grader/gradeapi/request.go +++ b/grader/gradeapi/request.go @@ -6,10 +6,15 @@ package gradeapi import ( "context" + "crypto" + "crypto/rsa" + "crypto/sha512" "github.com/benschlueter/delegatio/grader/gradeapi/gradeproto" "github.com/benschlueter/delegatio/grader/gradeapi/graders" + "github.com/benschlueter/delegatio/internal/config" "go.uber.org/zap" + "golang.org/x/crypto/ssh" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -25,6 +30,7 @@ func (a *API) RequestGrading(ctx context.Context, in *gradeproto.RequestGradingR var points int var log []byte var err error + uuid := in.GetStudentId() /* * How to authenticate the user? * The user signs the request with a private key and the server verifies the signature @@ -32,22 +38,17 @@ func (a *API) RequestGrading(ctx context.Context, in *gradeproto.RequestGradingR /* p, _ := peer.FromContext(ctx) requestEndpoint := p.Addr.String() */ // ToDO: use a unique ID from the USER - if in.GetSubmit() { - a.logger.Info("received grading request; verifying identity") - a.logger.Debug("nonce check passed", zap.String("nonce", in.GetNonce())) - // TODO: Verify identity - if err := a.checkNonce(ctx, in.GetNonce()); err != nil { - return nil, status.Error(codes.Unauthenticated, "nonce check failed") - } + a.logger.Info("received grading request; verifying identity") + if err := a.checkSignature(ctx, uuid, in.GetSignature(), in.GetSolution()); err != nil { + return nil, err } - toImplementName := "bschlueter" - grader, err := graders.NewGraders(a.logger.Named(toImplementName), toImplementName) + grader, err := graders.NewGraders(a.logger.Named(uuid), uuid) if err != nil { a.logger.Error("failed to create graders", zap.Error(err)) } - switch id := in.GetId(); id { + switch id := in.GetExerciseId(); id { case 1: a.logger.Info("received grading request for id 1") points, log, err = grader.GradeExerciseType1(ctx, in.GetSolution(), 1) @@ -58,13 +59,37 @@ func (a *API) RequestGrading(ctx context.Context, in *gradeproto.RequestGradingR a.logger.Info("received grading request for id 2") } - if err := a.updatePointsUser(ctx, points, toImplementName); err != nil { + if err := a.updatePointsUser(ctx, points, uuid); err != nil { return nil, status.Error(codes.Internal, "failed to update points") } return &gradeproto.RequestGradingResponse{Points: int32(points), Log: log}, nil } -func (a *API) checkNonce(_ context.Context, _ string) error { +func (a *API) checkSignature(_ context.Context, UUID string, signature, solution []byte) error { + a.logger.Info("checking signature", zap.String("studentID", UUID)) + exists, err := a.data().UUIDExists(UUID) + if err != nil { + return err + } + if !exists { + return status.Error(codes.NotFound, "user not found") + } + var userData config.UserInformation + if err := a.data().GetUUIDData(UUID, &userData); err != nil { + return status.Error(codes.FailedPrecondition, "failed to get user data") + } + a.logger.Info("got user data", zap.String("publicKey", string(userData.PubKey))) + sshPubKey, err := ssh.ParsePublicKey(userData.PubKey) + if err != nil { + return status.Error(codes.Internal, "failed to unmarshal public key") + } + pubKeyNewIface := sshPubKey.(ssh.CryptoPublicKey) + pubKewNewIfaceTwo := pubKeyNewIface.CryptoPublicKey() + rsaPubKey := pubKewNewIfaceTwo.(*rsa.PublicKey) + hashSolution := sha512.Sum512(solution) + if err := rsa.VerifyPKCS1v15(rsaPubKey, crypto.SHA512, hashSolution[:], signature); err != nil { + return status.Error(codes.Unauthenticated, "signature check") + } return nil } diff --git a/grader/server/run.go b/grader/server/run.go index 5c6ccfe..7d04f0b 100644 --- a/grader/server/run.go +++ b/grader/server/run.go @@ -33,7 +33,7 @@ var version = "0.0.0" func run(dialer gradeapi.Dialer, bindIP, bindPort string, zapLoggerCore *zap.Logger) { defer func() { _ = zapLoggerCore.Sync() }() zapLoggerCore.Info("starting delegatio grader", zap.String("version", version), zap.String("commit", config.Commit)) - gapi, err := gradeapi.New(zapLoggerCore.Named("gradeapi"), dialer) + gapi, err := gradeapi.New(zapLoggerCore.Named("gradeapi"), dialer, true) if err != nil { zapLoggerCore.Fatal("create gradeapi", zap.Error(err)) } diff --git a/grader/user/run.go b/grader/user/run.go index eda8486..577deac 100644 --- a/grader/user/run.go +++ b/grader/user/run.go @@ -8,11 +8,16 @@ package main import ( "context" + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha512" "os" gradeapi "github.com/benschlueter/delegatio/grader/gradeapi" "github.com/benschlueter/delegatio/internal/config" "go.uber.org/zap" + "golang.org/x/crypto/ssh" ) var version = "0.0.0" @@ -25,14 +30,39 @@ func run(dialer gradeapi.Dialer, zapLoggerCore *zap.Logger) { if len(os.Args) != 2 { zapLoggerCore.Fatal("usage: delegatio-agent ") } + solution, err := os.ReadFile(os.Args[1]) + if err != nil { + zapLoggerCore.Fatal("reading solution file", zap.Error(err)) + } + privKeyData, err := os.ReadFile("/root/.ssh/delegatio_priv_key") + if err != nil { + zapLoggerCore.Fatal("opening private key file ", zap.Error(err)) + } + signKeyIface, err := ssh.ParseRawPrivateKey(privKeyData) + if err != nil { + zapLoggerCore.Fatal("parsing private key", zap.Error(err)) + } + signKey, ok := signKeyIface.(*rsa.PrivateKey) + if !ok { + zapLoggerCore.Fatal("private key is not rsa") + } + hashSolution := sha512.Sum512(solution) + signature, err := rsa.SignPKCS1v15(rand.Reader, signKey, crypto.SHA512, hashSolution[:]) + if err != nil { + zapLoggerCore.Fatal("signing solution", zap.Error(err)) + } - api, err := gradeapi.New(zapLoggerCore, dialer) + api, err := gradeapi.New(zapLoggerCore, dialer, false) if err != nil { - zapLoggerCore.Fatal("failed to create gradeapi", zap.Error(err)) + zapLoggerCore.Fatal("create gradeapi", zap.Error(err)) } - points, err := api.SendGradingRequest(context.Background(), os.Args[1]) + points, err := api.SendGradingRequest( + context.Background(), + solution, + signature, + os.Getenv(config.UUIDEnvVariable)) if err != nil { - zapLoggerCore.Fatal("failed to send grading request", zap.Error(err)) + zapLoggerCore.Fatal("send grading request", zap.Error(err)) } zapLoggerCore.Info("received points", zap.Int("points", points)) } diff --git a/internal/config/global.go b/internal/config/global.go index b716f5a..ecd0c85 100644 --- a/internal/config/global.go +++ b/internal/config/global.go @@ -72,6 +72,8 @@ const ( NameSpaceFilePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" // SandboxPath is the path to the sandbox directory. SandboxPath = "/sandbox" + // UUIDEnvVariable is the environment variable name of the uuid of the user. + UUIDEnvVariable = "GraderUUID" ) // GetExampleConfig writes an example config to config.json. diff --git a/internal/k8sapi/templates/pod.go b/internal/k8sapi/templates/pod.go index e53c125..47d63c8 100644 --- a/internal/k8sapi/templates/pod.go +++ b/internal/k8sapi/templates/pod.go @@ -27,6 +27,10 @@ func Pod(identifier *config.KubeRessourceIdentifier) *coreAPI.PodSpec { }, }, }, + { + Name: config.UUIDEnvVariable, + Value: identifier.UserIdentifier, + }, }, /* Resources: coreAPI.ResourceRequirements{ Limits: coreAPI.ResourceList{ diff --git a/ssh/kubernetes/api.go b/ssh/kubernetes/api.go index 56b0ea7..2fa6da8 100644 --- a/ssh/kubernetes/api.go +++ b/ssh/kubernetes/api.go @@ -6,11 +6,8 @@ package kubernetes import ( "context" - "errors" "fmt" "net" - "os" - "strings" "time" "github.com/benschlueter/delegatio/agent/core" @@ -124,55 +121,5 @@ func (k *K8sAPIWrapper) CreatePodPortForward(ctx context.Context, conf *config.K // GetStore returns a store backed by kube etcd. Its only supposed to used within a kubernetes pod. func (k *K8sAPIWrapper) GetStore() (store.Store, error) { - var err error - var ns string - if _, err := os.Stat(config.NameSpaceFilePath); errors.Is(err, os.ErrNotExist) { - // ns is not ready when container spawns - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - ns, err = waitForNamespaceMount(ctx) - if err != nil { - k.logger.Error("failed to get namespace after timeout", zap.Error(err)) - return nil, err - } - } else { - // out of cluster mode currently assumes 'ssh' namespace - if content, err := os.ReadFile(config.NameSpaceFilePath); err == nil { - ns = strings.TrimSpace(string(content)) - } else { - return nil, err - } - } - k.logger.Info("namespace", zap.String("namespace", ns)) - configData, err := k.Client.GetConfigMapData(context.Background(), ns, "etcd-credentials") - if err != nil { - return nil, err - } - // logger.Info("config", zap.Any("configData", configData)) - etcdStore, err := store.NewEtcdStore([]string{net.JoinHostPort(configData["advertiseAddr"], "2379")}, k.logger, []byte(configData["caCert"]), []byte(configData["cert"]), []byte(configData["key"])) - if err != nil { - return nil, err - } - return etcdStore, nil -} - -// waitForNamespaceMount waits for the namespace file to be mounted and filled. -func waitForNamespaceMount(ctx context.Context) (string, error) { - t := time.NewTicker(100 * time.Millisecond) - defer t.Stop() - for { - select { - case <-ctx.Done(): - return "", ctx.Err() - case <-t.C: - data, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") - if err != nil && !errors.Is(err, os.ErrNotExist) { - return "", err - } - ns := strings.TrimSpace(string(data)) - if len(ns) != 0 { - return ns, nil - } - } - } + return k.Client.GetStore() } diff --git a/ssh/server.go b/ssh/server.go index 2470ad4..e17cc0c 100644 --- a/ssh/server.go +++ b/ssh/server.go @@ -62,7 +62,7 @@ func (s *Server) Start(ctx context.Context) { encodeKey := base64.StdEncoding.EncodeToString(key.Marshal()) s.log.Debug("publickeycallback called", zap.String("user", conn.User()), zap.Binary("session", conn.SessionID()), zap.String("key", encodeKey)) - err := s.data().GetPublicKeyData(string(ssh.MarshalAuthorizedKey(key)), &userData) + err := s.data().GetPublicKeyData(string(key.Marshal()), &userData) if err != nil { s.log.Error("failed to obtain user data", zap.Error(err)) return nil, fmt.Errorf("failed to obtain user data: %w", err) @@ -103,6 +103,9 @@ func (s *Server) Start(ctx context.Context) { } s.log.Debug("public key created and stored", zap.String("key", string(userData.PubKey))) } + if err := s.data().GetUUIDData(userData.UUID, userData); err != nil { + return nil, fmt.Errorf("getting user data: %w", err) + } s.log.Debug("private key", zap.String("key", string(userData.PrivKey))) return &ssh.Permissions{ Extensions: map[string]string{ diff --git a/ssh/util/util.go b/ssh/util/util.go index a5a866d..d11f300 100644 --- a/ssh/util/util.go +++ b/ssh/util/util.go @@ -25,5 +25,5 @@ func CreateSSHKeypair() ([]byte, []byte, error) { if err != nil { return nil, nil, err } - return privKeyMem, ssh.MarshalAuthorizedKey(pub), nil + return privKeyMem, pub.Marshal(), nil }