From f86b7824cb9dcefd3d7485922fe979ceed8a0d8a Mon Sep 17 00:00:00 2001 From: Bharat Kathi Date: Thu, 29 Aug 2024 14:22:18 -0700 Subject: [PATCH 1/5] add keygen script for rs256 --- scripts/keygen.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100755 scripts/keygen.sh diff --git a/scripts/keygen.sh b/scripts/keygen.sh new file mode 100755 index 0000000..b95b28a --- /dev/null +++ b/scripts/keygen.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key +openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub + +echo "RSA_PUBLIC_KEY=\"$(awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' jwtRS256.key)\"" +echo "RSA_PRIVATE_KEY=\"$(awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' jwtRS256.key.pub)\"" + +# Clean up temporary files +rm jwtRS256.key jwtRS256.key.pub \ No newline at end of file From df3c0a499a269ba8a5fc89e4fcff025a5347c58d Mon Sep 17 00:00:00 2001 From: Bharat Kathi Date: Thu, 29 Aug 2024 14:28:19 -0700 Subject: [PATCH 2/5] updated jwt functions to use rs keys --- .gitignore | 5 ++++- config/config.go | 11 +++++++++- main.go | 46 +++++++++++++++++++++++++---------------- scripts/keygen.sh | 6 +++--- service/auth_service.go | 21 ++++++++++++++++--- utils/config.go | 13 +++++++++++- 6 files changed, 75 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index f9970e1..aa20365 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,7 @@ drive-service-account.json # Go workspace file go.work -coverage.html \ No newline at end of file +coverage.html + +jwtRS256.key +jwtRS256.key.pub \ No newline at end of file diff --git a/config/config.go b/config/config.go index 69d7eb1..29ed3d7 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,9 @@ package config -import "os" +import ( + "crypto/rsa" + "os" +) var Version = "3.2.13" var Env = os.Getenv("ENV") @@ -38,6 +41,12 @@ var SubteamRoleNames = []string{"Aero", "Business", "Chassis", "Data", "Electron var AuthSigningKey = os.Getenv("AUTH_SIGNING_KEY") +var RsaPublicKey *rsa.PublicKey +var RsaPrivateKey *rsa.PrivateKey + +var RsaPublicKeyString = os.Getenv("RSA_PUBLIC_KEY") +var RsaPrivateKeyString = os.Getenv("RSA_PRIVATE_KEY") + var MemberDirectorySheetID = "1reuLZox2daj8r2H-lZrwB4oFPYlJ6oC7983UUaZd6AY" var DriveCron = os.Getenv("DRIVE_CRON") diff --git a/main.go b/main.go index 474937b..aaf78c7 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,7 @@ package main import ( - "sentinel/commands" "sentinel/config" - "sentinel/controller" - "sentinel/database" "sentinel/service" "sentinel/utils" ) @@ -15,22 +12,35 @@ func main() { utils.VerifyConfig() defer utils.Logger.Sync() - database.InitializeDB() - service.InitializeDrive() - service.ConnectDiscord() - service.InitializeRoles() - service.InitializeSubteams() - go service.SyncRolesForAllUsers() - commands.InitializeDiscordBot() - controller.RegisterDriveCronJob() - controller.RegisteGithubCronJob() - controller.RegisterWikiCronJob() - // service.FindAllNonVerifiedUsers() + service.InitializeKeys() - router := controller.SetupRouter() - controller.InitializeRoutes(router) - err := router.Run(":" + config.Port) + // database.InitializeDB() + // service.InitializeDrive() + // service.ConnectDiscord() + // service.InitializeRoles() + // service.InitializeSubteams() + // go service.SyncRolesForAllUsers() + // commands.InitializeDiscordBot() + // controller.RegisterDriveCronJob() + // controller.RegisteGithubCronJob() + // controller.RegisterWikiCronJob() + + token, err := service.GenerateJWT("123", "test@test.com", "sentinel:all", "sentinel") if err != nil { - utils.SugarLogger.Fatalln(err) + utils.SugarLogger.Errorln(err) } + utils.SugarLogger.Infoln(token) + + claims, err := service.ValidateJWT(token) + if err != nil { + utils.SugarLogger.Errorln(err) + } + utils.SugarLogger.Infoln(claims) + + // router := controller.SetupRouter() + // controller.InitializeRoutes(router) + // err := router.Run(":" + config.Port) + // if err != nil { + // utils.SugarLogger.Fatalln(err) + // } } diff --git a/scripts/keygen.sh b/scripts/keygen.sh index b95b28a..6e80a7b 100755 --- a/scripts/keygen.sh +++ b/scripts/keygen.sh @@ -3,8 +3,8 @@ ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub -echo "RSA_PUBLIC_KEY=\"$(awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' jwtRS256.key)\"" -echo "RSA_PRIVATE_KEY=\"$(awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' jwtRS256.key.pub)\"" +echo "RSA_PUBLIC_KEY=\"$(awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' jwtRS256.key.pub)\"" +echo "RSA_PRIVATE_KEY=\"$(awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' jwtRS256.key)\"" # Clean up temporary files -rm jwtRS256.key jwtRS256.key.pub \ No newline at end of file +rm jwtRS256.key jwtRS256.key.pub diff --git a/service/auth_service.go b/service/auth_service.go index be72f10..c4014be 100644 --- a/service/auth_service.go +++ b/service/auth_service.go @@ -14,6 +14,21 @@ import ( "golang.org/x/crypto/bcrypt" ) +func InitializeKeys() { + // Parse the RSA public key + publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(config.RsaPublicKeyString)) + if err != nil { + utils.SugarLogger.Errorln("Failed to parse RSA public key:", err) + } + config.RsaPublicKey = publicKey + // Parse the RSA private key + privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(config.RsaPrivateKeyString)) + if err != nil { + utils.SugarLogger.Errorln("Failed to parse RSA private key:", err) + } + config.RsaPrivateKey = privateKey +} + func RegisterEmailPassword(email string, password string) (string, error) { user := GetUserByEmail(email) if user.ID == "" { @@ -104,8 +119,8 @@ func GenerateJWT(id string, email string, scope string, client_id string) (strin }, } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - signedToken, err := token.SignedString([]byte(config.AuthSigningKey)) + token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) + signedToken, err := token.SignedString(config.RsaPrivateKey) if err != nil { utils.SugarLogger.Errorln(err.Error()) return "", err @@ -116,7 +131,7 @@ func GenerateJWT(id string, email string, scope string, client_id string) (strin func ValidateJWT(token string) (*model.AuthClaims, error) { claims := &model.AuthClaims{} _, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) { - return []byte(config.AuthSigningKey), nil + return config.RsaPublicKey, nil }) if err != nil { utils.SugarLogger.Errorln(err.Error()) diff --git a/utils/config.go b/utils/config.go index ba48e91..dfa76f6 100644 --- a/utils/config.go +++ b/utils/config.go @@ -1,6 +1,9 @@ package utils -import "sentinel/config" +import ( + "sentinel/config" + "strings" +) func VerifyConfig() { if config.Port == "" { @@ -44,6 +47,14 @@ func VerifyConfig() { if config.AuthSigningKey == "" { SugarLogger.Errorf("AUTH_SIGNING_KEY is not set") } + if config.RsaPublicKeyString == "" { + SugarLogger.Errorf("RSA_PUBLIC_KEY is not set") + } + config.RsaPublicKeyString = strings.ReplaceAll(config.RsaPublicKeyString, "\\n", "\n") + if config.RsaPrivateKeyString == "" { + SugarLogger.Errorf("RSA_PRIVATE_KEY is not set") + } + config.RsaPrivateKeyString = strings.ReplaceAll(config.RsaPrivateKeyString, "\\n", "\n") if config.DriveCron == "" { config.DriveCron = "0 * * * *" SugarLogger.Infof("DRIVE_CRON is not set, defaulting to %s", config.DriveCron) From a6b7fc49e5420fcbc91bafc8d60fa12bb830dfa2 Mon Sep 17 00:00:00 2001 From: Bharat Kathi Date: Thu, 29 Aug 2024 14:52:06 -0700 Subject: [PATCH 3/5] add jwks endpoint --- config/config.go | 1 + controller/auth_controller.go | 5 ++++ controller/route_controller.go | 1 + main.go | 44 +++++++++++++--------------------- service/auth_service.go | 24 ++++++++++++++++++- 5 files changed, 47 insertions(+), 28 deletions(-) diff --git a/config/config.go b/config/config.go index 29ed3d7..5ba773f 100644 --- a/config/config.go +++ b/config/config.go @@ -43,6 +43,7 @@ var AuthSigningKey = os.Getenv("AUTH_SIGNING_KEY") var RsaPublicKey *rsa.PublicKey var RsaPrivateKey *rsa.PrivateKey +var RsaPublicKeyJWKS map[string]interface{} var RsaPublicKeyString = os.Getenv("RSA_PUBLIC_KEY") var RsaPrivateKeyString = os.Getenv("RSA_PRIVATE_KEY") diff --git a/controller/auth_controller.go b/controller/auth_controller.go index 48d825d..3a5017a 100644 --- a/controller/auth_controller.go +++ b/controller/auth_controller.go @@ -2,6 +2,7 @@ package controller import ( "net/http" + "sentinel/config" "sentinel/model" "sentinel/service" "sentinel/utils" @@ -9,6 +10,10 @@ import ( "github.com/gin-gonic/gin" ) +func GetJWKS(c *gin.Context) { + c.JSON(http.StatusOK, config.RsaPublicKeyJWKS) +} + func RegisterAccountPassword(c *gin.Context) { RequireAny(c, RequestTokenHasScope(c, "sentinel:all")) diff --git a/controller/route_controller.go b/controller/route_controller.go index ec79b34..92e2d4b 100644 --- a/controller/route_controller.go +++ b/controller/route_controller.go @@ -31,6 +31,7 @@ func SetupRouter() *gin.Engine { func InitializeRoutes(router *gin.Engine) { router.GET("/ping", Ping) + router.GET("/auth/keys.json", GetJWKS) router.POST("/auth/register", RegisterAccountPassword) router.POST("/auth/login", LoginAccount) router.POST("/auth/login/discord", LoginDiscord) diff --git a/main.go b/main.go index aaf78c7..1cdbe93 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,10 @@ package main import ( + "sentinel/commands" "sentinel/config" + "sentinel/controller" + "sentinel/database" "sentinel/service" "sentinel/utils" ) @@ -12,35 +15,22 @@ func main() { utils.VerifyConfig() defer utils.Logger.Sync() + database.InitializeDB() service.InitializeKeys() + service.InitializeDrive() + service.ConnectDiscord() + service.InitializeRoles() + service.InitializeSubteams() + go service.SyncRolesForAllUsers() + commands.InitializeDiscordBot() + controller.RegisterDriveCronJob() + controller.RegisteGithubCronJob() + controller.RegisterWikiCronJob() - // database.InitializeDB() - // service.InitializeDrive() - // service.ConnectDiscord() - // service.InitializeRoles() - // service.InitializeSubteams() - // go service.SyncRolesForAllUsers() - // commands.InitializeDiscordBot() - // controller.RegisterDriveCronJob() - // controller.RegisteGithubCronJob() - // controller.RegisterWikiCronJob() - - token, err := service.GenerateJWT("123", "test@test.com", "sentinel:all", "sentinel") + router := controller.SetupRouter() + controller.InitializeRoutes(router) + err := router.Run(":" + config.Port) if err != nil { - utils.SugarLogger.Errorln(err) + utils.SugarLogger.Fatalln(err) } - utils.SugarLogger.Infoln(token) - - claims, err := service.ValidateJWT(token) - if err != nil { - utils.SugarLogger.Errorln(err) - } - utils.SugarLogger.Infoln(claims) - - // router := controller.SetupRouter() - // controller.InitializeRoutes(router) - // err := router.Run(":" + config.Port) - // if err != nil { - // utils.SugarLogger.Fatalln(err) - // } } diff --git a/service/auth_service.go b/service/auth_service.go index c4014be..f99d250 100644 --- a/service/auth_service.go +++ b/service/auth_service.go @@ -1,7 +1,10 @@ package service import ( + "crypto/rsa" + "encoding/base64" "fmt" + "math/big" "sentinel/config" "sentinel/database" "sentinel/model" @@ -27,6 +30,25 @@ func InitializeKeys() { utils.SugarLogger.Errorln("Failed to parse RSA private key:", err) } config.RsaPrivateKey = privateKey + config.RsaPublicKeyJWKS = PublicKeyToJWKS(publicKey) +} + +func PublicKeyToJWKS(publicKey *rsa.PublicKey) map[string]interface{} { + e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(publicKey.E)).Bytes()) + n := base64.RawURLEncoding.EncodeToString(publicKey.N.Bytes()) + + return map[string]interface{}{ + "keys": []map[string]interface{}{ + { + "kty": "RSA", + "use": "sig", + "alg": "RS256", + "kid": "1", + "n": n, + "e": e, + }, + }, + } } func RegisterEmailPassword(email string, password string) (string, error) { @@ -112,7 +134,7 @@ func GenerateJWT(id string, email string, scope string, client_id string) (strin Scope: scope, RegisteredClaims: jwt.RegisteredClaims{ ID: id, - Issuer: "sso.gauchoracing.com", + Issuer: "https://sso.gauchoracing.com/", Audience: jwt.ClaimStrings{client_id}, IssuedAt: jwt.NewNumericDate(time.Now()), ExpiresAt: jwt.NewNumericDate(expirationTime), From d4c367c1abc1ff72935ef6b3c041123bbe6ee4c1 Mon Sep 17 00:00:00 2001 From: Bharat Kathi Date: Thu, 29 Aug 2024 14:58:04 -0700 Subject: [PATCH 4/5] add some rudimentary oidc scope support --- model/oauth.go | 3 +++ service/oauth_service.go | 10 +++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/model/oauth.go b/model/oauth.go index 3f0b262..75230b9 100644 --- a/model/oauth.go +++ b/model/oauth.go @@ -5,6 +5,9 @@ import ( ) var ValidOauthScopes = map[string]string{ + "openid": "OpenID Connect scope", + "profile": "OIDC profile scope", + "email": "OIDC email scope", "user:read": "Read user account information", "user:write": "Edit user account information", "drive:read": "Read user's team drive access information", diff --git a/service/oauth_service.go b/service/oauth_service.go index 9aa26c0..cbef2b3 100644 --- a/service/oauth_service.go +++ b/service/oauth_service.go @@ -137,11 +137,19 @@ func ValidateScope(scopes string) bool { func GenerateAuthorizationCode(clientID, userID, scope string) (model.AuthorizationCode, error) { code := generateCryptoString(8) expiresAt := time.Now().Add(5 * time.Minute) + + // Check if scope contains "oidc" and add "user:read" if it does + scopes := strings.Split(scope, " ") + if contains(scopes, "oidc") && !contains(scopes, "user:read") { + scopes = append(scopes, "user:read") + } + updatedScope := strings.Join(scopes, " ") + authCode := model.AuthorizationCode{ Code: code, ClientID: clientID, UserID: userID, - Scope: scope, + Scope: updatedScope, ExpiresAt: utils.WithPrecision(expiresAt), } result := database.DB.Create(&authCode) From 9ce53a84fb1e5661024861705f25ba124c099e2f Mon Sep 17 00:00:00 2001 From: Bharat Kathi Date: Thu, 29 Aug 2024 15:15:12 -0700 Subject: [PATCH 5/5] aight we good gang --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 5ba773f..3820eea 100644 --- a/config/config.go +++ b/config/config.go @@ -5,7 +5,7 @@ import ( "os" ) -var Version = "3.2.13" +var Version = "3.3.0" var Env = os.Getenv("ENV") var Port = os.Getenv("PORT") var Prefix = os.Getenv("PREFIX")