From 24529c591d073105bcc7274a627c0deaada345ee Mon Sep 17 00:00:00 2001 From: sahil Date: Mon, 10 Apr 2023 03:44:03 +0530 Subject: [PATCH] feat(license): implement license validation Signed-off-by: sahil --- src/cluster/k8sClientHandler.go | 76 +++++++++++++------ src/go.mod | 1 + src/go.sum | 2 + src/license/license.go | 130 ++++++++++++++++++++++++++++++-- src/license/secrets.go | 9 --- src/license/server.go | 19 ++++- src/main.go | 19 ++++- src/server/grpcServer.go | 23 ++++-- 8 files changed, 230 insertions(+), 49 deletions(-) delete mode 100644 src/license/secrets.go diff --git a/src/cluster/k8sClientHandler.go b/src/cluster/k8sClientHandler.go index 439e2cd6..0d08e243 100644 --- a/src/cluster/k8sClientHandler.go +++ b/src/cluster/k8sClientHandler.go @@ -503,13 +503,13 @@ func GetKubearmorRelayURL() string { return url } -func GetSecrets(k8sClient *kubernetes.Clientset) (*v1.Secret, error) { +func GetSecrets(k8sClient *kubernetes.Clientset, label string) (*v1.Secret, error) { if k8sClient == nil { return nil, errors.New("k8s client not created") } secrets, err := k8sClient.CoreV1().Secrets("").List(context.Background(), metav1.ListOptions{ - LabelSelector: "license=discovery-engine", + LabelSelector: label, }) if err != nil { @@ -520,53 +520,83 @@ func GetSecrets(k8sClient *kubernetes.Clientset) (*v1.Secret, error) { return nil, nil } + if len(secrets.Items) > 1 { + return nil, errors.New("error while getting secrets, error: more than 1 secrets exists for license") + } + + return &secrets.Items[0], nil + } -func CreateSecret(k8sClient *kubernetes.Clientset) (*v1.Secret, error) { +func CreateLicenseSecret(k8sClient *kubernetes.Clientset, key string, userId string) (*v1.Secret, error) { if k8sClient == nil { return nil, errors.New("k8s client not created") } - secret, err := GetSecrets(k8sClient) + secret, err := GetSecrets(k8sClient, "app:discovery-engine") if err != nil { - log.Error().Msgf(err.Error()) + log.Error().Msgf("error while fetching secrets, error: %s", err.Error()) return nil, err } if secret != nil { return secret, nil } + t := true - scrt := v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{}, - Immutable: nil, - Data: nil, - StringData: nil, - Type: "", + secretSpec := v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app": "discovery-engine", + }, + }, + Immutable: &t, + Data: nil, + StringData: map[string]string{ + "key": key, + "user-id": userId, + }, + Type: v1.SecretTypeOpaque, } - secret, err := k8sClient.CoreV1().Secrets("").Create(context.Background(), &scrt, metav1.CreateOptions{}) + secret, err = k8sClient.CoreV1().Secrets("").Create(context.Background(), &secretSpec, metav1.CreateOptions{}) + if err != nil { + log.Error().Msgf("error while creating secret for license key, error: %s", err.Error()) + return nil, err + } + return secret, nil } -func DeleteSecret(k8sClient *kubernetes.Clientset, secretName string) error { +func DeleteSecrets(k8sClient *kubernetes.Clientset, label string) error { if k8sClient == nil { return errors.New("k8s client not created") } - _, err := k8sClient.CoreV1().Secrets("").Delete(context.Background(), secretName, metav1.DeleteOptions{ - TypeMeta: metav1.TypeMeta{}, - GracePeriodSeconds: nil, - Preconditions: nil, - OrphanDependents: nil, - PropagationPolicy: nil, - DryRun: nil, - }) - + err := k8sClient.CoreV1().Secrets("").DeleteCollection(context.Background(), metav1.DeleteOptions{}, + metav1.ListOptions{ + LabelSelector: label, + }) if err != nil { + log.Error().Msgf("error while deleting secrets for discovery-engine, error: %s", err.Error()) return err } return nil } + +func GetKubeSystemUUID(k8sClient *kubernetes.Clientset) (string, error) { + if k8sClient == nil { + return "", errors.New("k8s client not created") + } + + kubeSystem, err := k8sClient.CoreV1().Namespaces().Get(context.Background(), "kube-system", metav1.GetOptions{}) + if err != nil { + return "", err + } + + uuid := string(kubeSystem.GetUID()) + + return uuid, nil + +} diff --git a/src/go.mod b/src/go.mod index 1223db85..f21f050f 100644 --- a/src/go.mod +++ b/src/go.mod @@ -87,6 +87,7 @@ require ( github.com/magiconair/properties v1.8.5 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mervick/aes-everywhere/go/aes256 v0.0.0-20220903070135-f13ed3789ae1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect diff --git a/src/go.sum b/src/go.sum index 2221a87c..4de0a933 100644 --- a/src/go.sum +++ b/src/go.sum @@ -687,6 +687,8 @@ github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuri github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8= github.com/mdlayher/raw v0.0.0-20190313224157-43dbcdd7739d/go.mod h1:r1fbeITl2xL/zLbVnNHFyOzQJTgr/3fpf1lJX/cjzR8= github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= +github.com/mervick/aes-everywhere/go/aes256 v0.0.0-20220903070135-f13ed3789ae1 h1:RLYNaO6dcj6hms2FSzwsXlZsyjxQBJi8qO/8Vkilhz0= +github.com/mervick/aes-everywhere/go/aes256 v0.0.0-20220903070135-f13ed3789ae1/go.mod h1:Eb5RMoo9kOQra/2uRiUTGP+LfNuM13Vqm7y7P34+KKo= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= diff --git a/src/license/license.go b/src/license/license.go index 631a5110..68976e18 100644 --- a/src/license/license.go +++ b/src/license/license.go @@ -1,28 +1,142 @@ package license -import "github.com/golang-jwt/jwt" +import ( + "errors" + "github.com/accuknox/auto-policy-discovery/src/cluster" + "github.com/golang-jwt/jwt" + "github.com/mervick/aes-everywhere/go/aes256" + "github.com/rs/zerolog/log" + "k8s.io/client-go/kubernetes" + "strings" +) + +var privateKey = []byte("-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDZRSpD6aSjfkeH\nUQv5gPzMw3jPf7izocKLsK9EDQrFFUlsosDMwklGHb9sRXR+sR0mLuzbV4r3KWCZ\ncdI57FiFL3N+7MLHCEKW0BmlddAItqgVqvD9hd6r7pZonC0Jlmw2fSPlSLdNuKov\nZf1wX3NK4dqDZ0o7X4rQvJ/15mL/q6f4BvIQ2ay4LtXl72V+xGl5HNb/aAIjMnkt\nTKSF5Bhmfwh/G1O6Qtm1R/dT/WpAqfwWQlXRoUCOzsSAsQDaQ5OQOkRAWGH9CXVg\n645PHfYssAMGpEGS/GcI6kDaZXJ5nO9mVAFkmmi1cGZ/Y+CVYj5n8bBAiXoOpfnb\nrdgSPqllAgMBAAECggEAHEIYZ+pOsTv2miUioVITLx+aSiSStXxLtQbnGLr4hF8L\nJG+7kzYOuSmb0L1s1CB9ic6PHE0TxCczyulIniN89N+RzjeFcsUCgiJxP3ml88gr\n7tuDifm8FxEKK3NqkSVpECIxBbxyKM/khJMnhhLICDx9Qttvhm7b+1+ZD26mQBnK\nJCCXCc0LocXL1shfy9ot4rKjF+lbkd2zC6NorQDfCl4eMaWZZuNjNdeaRSzsWn+u\nJVA3HjAdUTzvZBPHCFTvqbwWJ1/IGEc2FiQhrIHLy2ZJ+HjcQh3/HkDfS8PHQ6WZ\nU3GA51Ulu380DW0ZSeoQn+IlLkJ3RIaDXph4BF6V0QKBgQD6DlmEMpT8rsnDljH1\noly5Sy45T7RxG4EYsD2q3/kyEaJSJkyz5/bc0OCiD3m2+ub2G8561hu2opKLkFgf\nk60q82EDS7nupmMUbuioAT2Z/107fs+uGGb8GRkqLSBCdHlqDXyJqw/H530J082B\nuZokmd0/FCiOnvzy8XcU3wNF8QKBgQDeb047d6fS0E9zju6beHvJ5FlJelFk8ubF\no7StRmTV2c8oYIRZc+wSesP/r4ipmLLtrmV0z4e+/y+7ZcJNdiHB/tVtgwLDnekn\nvjgYTHc5twhzKHLFlz5CoatVrvjyMqiy4mil1scfIxVKhqgIC66tE7KLk2t/jDqa\niPCEiiOWtQKBgQDxOxawqfuBKT4MKcFYrqG1QAn/0BzLYTVRk/Rp/FhzLP/jMH+e\nr5E/xWJv9W3+UF6v3nN7nUJvFrc5XK8nB1hvrwtQGqXszSeYdsaMDWZYQjq3Qscn\ntPLlEXFtdstAUQ0weoJCbXxz5aaC7Im9NEi4NpdyWwglTsvHs5qlz99ggQKBgD7V\ncGMKFQVPRPJe0PZoHYfVkodYH0AGAbyY1wQPm5JxWbyNLzXZsjkyJsXGfzNxaDIO\nDqjlqvIAQiqMv0uEcFrNstqhpJk/tUo9yLjMeO17z85AAdPhOnw2ESE+MSKxvsfx\nfyBBwLQCBBSuXy8OpcRMiSY2eappIXDf+BlosE71AoGBAIT3eGRR61+oRMIceYih\nL/TAMwmS4ZVNANrMhMw6U7Qy6myfnyUacXeY2GGJhWseYS6LhEI5kjYD+W1v7hK1\nbN0VkBMLpc3Y5w/FMpfUvE9X1lWzHZucVWw7LHWVnjgPdWlf23m11NujrPPab+9N\ndTLggo4D7JL2dfoXhr3U9O/A\n-----END PRIVATE KEY-----\n") + +type ConfigLicense struct { + k8sClient *kubernetes.Clientset +} type License struct { - UserId string - Key string + UserId string + Key string + PlatformUUID string +} + +var cfg *ConfigLicense +var Tkn *Token + +func InitializeConfig(k8sClient *kubernetes.Clientset) { + cfg = &ConfigLicense{k8sClient: k8sClient} +} + +func CheckLicenseSecret() error { + log.Info().Msgf("fetching license secrets to validate discovery-engine licensing") + secret, err := cluster.GetSecrets(cfg.k8sClient, "app:discovery-engine") + if err != nil { + log.Error().Msgf("error while fetching secrets for discovery engine licensing, error: %s", err.Error()) + return err + } + if secret == nil { + return nil + } + + l := &License{ + UserId: secret.StringData["user-id"], + Key: secret.StringData["key"], + } + err = l.ValidateLicense() + + if err != nil { + log.Error().Msgf("error while validating license retrieved through secrets, error: %s", err.Error()) + return err + } + log.Info().Msgf("license validation successfully for user-id: %s with key: %s", l.UserId, l.Key) + return nil } func (l *License) ValidateLicense() error { + var err error + + l.PlatformUUID, err = cfg.getKubeSystemUUID() + if err != nil { + log.Error().Msgf("error while fetching uuid of kube-system namespace, error: %s", err.Error()) + return err + } + + decryptedKey, err := decryptKey(l.Key, l.PlatformUUID) + if err != nil { + log.Error().Msgf("error while decrypting license key, error: %s", err.Error()) + return err + } + + Tkn, err = validateToken(decryptedKey) + if err != nil { + log.Error().Msgf("error while validating jwt token") + return err + } + + log.Info().Msgf("license validation successfully for user: %s with license key: %s", l.UserId, l.Key) + + secret, err := cluster.CreateLicenseSecret(cfg.k8sClient, l.Key, l.UserId) + if err != nil { + log.Error().Msgf("error while creating secret for discovery engine license, error: %s", err.Error()) + return err + } + + log.Info().Msgf("secret created for discovery engine license with name: %s and uuid: %d", secret.GetName(), secret.GetUID()) return nil } +func (cfg *ConfigLicense) getKubeSystemUUID() (string, error) { + uuid, err := cluster.GetKubeSystemUUID(cfg.k8sClient) + if err != nil { + log.Error().Msgf("error while fetching uuid of kube-system namespace, error: %s", err.Error()) + return "", err + } + return uuid, nil +} + +func decryptKey(key string, platformUUID string) (string, error) { + decryptedKey := aes256.Decrypt(key, platformUUID) + tokenSplit := strings.Split(decryptedKey, ".") + if len(tokenSplit) != 3 { + log.Error().Msgf("invalid licence key") + return "", errors.New("invalid license key") + } + return decryptedKey, nil +} + type Token struct { - jwt.Claims + jwt *jwt.Token + claims *jwt.MapClaims } -func (t *Token) ValidateToken() error { - return nil +func validateToken(decryptedKey string) (*Token, error) { + + claims := jwt.MapClaims{} + + jwtToken, err := jwt.ParseWithClaims(decryptedKey, claims, func(token *jwt.Token) (interface{}, error) { + log.Info().Msgf("Signature: %v", token.Header) + return privateKey, nil + }) + if err != nil { + log.Error().Msgf("error while parsing jwt token, error: %s", err.Error()) + return nil, err + } + return &Token{ + jwt: jwtToken, + claims: &claims, + }, nil } -func checkUserID(c *jwt.Claims) bool { +func (t *Token) checkUserID() bool { return false } -func getFeatures(c *jwt.Claims) []string { +func (t *Token) getFeatures() []string { + return nil +} + +func WatchFeatures(features []string, expTime string) error { return nil } diff --git a/src/license/secrets.go b/src/license/secrets.go deleted file mode 100644 index 1c3c739f..00000000 --- a/src/license/secrets.go +++ /dev/null @@ -1,9 +0,0 @@ -package license - -func CreateSecret() error { - return nil -} - -func GetSecret() error { - return nil -} diff --git a/src/license/server.go b/src/license/server.go index 7c698306..74392053 100644 --- a/src/license/server.go +++ b/src/license/server.go @@ -3,15 +3,28 @@ package license import ( "context" ipb "github.com/accuknox/auto-policy-discovery/src/protobuf/v1/license" + "github.com/rs/zerolog/log" ) type Server struct { - ipb.LicenseServer + ipb.UnimplementedLicenseServer } -func (s *Server) InstallLicense(ctx context.Context, l *ipb.LicenseRequest) (*ipb.LicenseResponse, error) { +func (ls *Server) InstallLicense(ctx context.Context, lr *ipb.LicenseRequest) (*ipb.LicenseResponse, error) { + log.Info().Msgf("request received to install license for user-id: %s", lr.UserId) + l := License{ + UserId: lr.UserId, + Key: lr.Key, + } + err := l.ValidateLicense() + if err != nil { + return &ipb.LicenseResponse{ + Res: -1, + Message: "error while validating license", + }, err + } return &ipb.LicenseResponse{ Res: 0, - Message: "", + Message: "license installed successfully", }, nil } diff --git a/src/main.go b/src/main.go index f1c58896..71a345f9 100644 --- a/src/main.go +++ b/src/main.go @@ -3,6 +3,7 @@ package main import ( "github.com/accuknox/auto-policy-discovery/src/cluster" "github.com/accuknox/auto-policy-discovery/src/config" + "github.com/accuknox/auto-policy-discovery/src/license" "math/rand" "net" "os" @@ -52,17 +53,33 @@ func init() { // ========== // func main() { + + // check for license secret, if exist then validate + err := license.CheckLicenseSecret() + if err != nil { + log.Error().Msgf("error while validating license, error: %s", err.Error()) + } + + license.InitializeConfig(cfg.K8sClient) + // create server lis, err := net.Listen("tcp", ":"+grpcserver.PortNumber) if err != nil { log.Error().Msgf("gRPC server failed to listen: %v", err) os.Exit(1) } - server := grpcserver.GetNewServer() + + // starts grpc server + server := grpcserver.StartGrpcServer() + // add license server + server = grpcserver.AddLicenseServer(server) + + server = grpcserver.AddServers(server) // start autopolicy service log.Info().Msgf("gRPC server on %s port started", grpcserver.PortNumber) if err := server.Serve(lis); err != nil { log.Error().Msgf("Failed to serve: %v", err) } + } diff --git a/src/server/grpcServer.go b/src/server/grpcServer.go index 743b0f7f..e2061b03 100644 --- a/src/server/grpcServer.go +++ b/src/server/grpcServer.go @@ -3,6 +3,7 @@ package server import ( "context" "errors" + "github.com/accuknox/auto-policy-discovery/src/license" "strings" "github.com/rs/zerolog" @@ -22,6 +23,7 @@ import ( fpb "github.com/accuknox/auto-policy-discovery/src/protobuf/v1/consumer" dpb "github.com/accuknox/auto-policy-discovery/src/protobuf/v1/discovery" ipb "github.com/accuknox/auto-policy-discovery/src/protobuf/v1/insight" + lpb "github.com/accuknox/auto-policy-discovery/src/protobuf/v1/license" opb "github.com/accuknox/auto-policy-discovery/src/protobuf/v1/observability" ppb "github.com/accuknox/auto-policy-discovery/src/protobuf/v1/publisher" wpb "github.com/accuknox/auto-policy-discovery/src/protobuf/v1/worker" @@ -281,16 +283,27 @@ func (ps *publisherServer) GetSummary(req *ppb.SummaryRequest, srv ppb.Publisher return obs.SysSummary.RelaySummaryEventToGrpcStream(srv, consumer) } -// ================= // -// == gRPC server == // -// ================= // - -func GetNewServer() *grpc.Server { +func StartGrpcServer() *grpc.Server { s := grpc.NewServer() grpc_health_v1.RegisterHealthServer(s, health.NewServer()) reflection.Register(s) + return s +} + +func AddLicenseServer(s *grpc.Server) *grpc.Server { + licenseServer := &license.Server{} + lpb.RegisterLicenseServer(s, licenseServer) + return s +} + +// ================= // +// == gRPC server == // +// ================= // + +func AddServers(s *grpc.Server) *grpc.Server { + // create server instances workerServer := &workerServer{} consumerServer := &consumerServer{}