fix: segregate env setup

This commit is contained in:
Lakhan Samani 2022-02-26 09:44:55 +05:30
parent 332269ecf9
commit 4e19f73845
9 changed files with 478 additions and 84 deletions

View File

@ -20,6 +20,8 @@ const (
EnvKeyAuthorizerURL = "AUTHORIZER_URL"
// EnvKeyPort key for env variable PORT
EnvKeyPort = "PORT"
// EnvKeyClientID key for env variable CLIENT_ID
EnvKeyClientID = "CLIENT_ID"
// EnvKeyAdminSecret key for env variable ADMIN_SECRET
EnvKeyAdminSecret = "ADMIN_SECRET"

137
server/crypto/ecdsa.go Normal file
View File

@ -0,0 +1,137 @@
package crypto
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"errors"
)
// NewECDSAKey to generate new ECDSA Key if env is not set
func NewECDSAKey() (*ecdsa.PrivateKey, string, string, error) {
key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
return nil, "", "", err
}
privateKey, publicKey, err := AsECDSAStr(key, &key.PublicKey)
if err != nil {
return nil, "", "", err
}
return key, privateKey, publicKey, err
}
// IsECDSA checks if given string is valid ECDSA algo
func IsECDSA(algo string) bool {
switch algo {
case "ES256", "ES384", "ES512":
return true
default:
return false
}
}
// ExportEcdsaPrivateKeyAsPemStr to get ECDSA private key as pem string
func ExportEcdsaPrivateKeyAsPemStr(privkey *ecdsa.PrivateKey) (string, error) {
privkeyBytes, err := x509.MarshalECPrivateKey(privkey)
if err != nil {
return "", err
}
privkeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "ECDSA PRIVATE KEY",
Bytes: privkeyBytes,
},
)
return string(privkeyPem), nil
}
// ExportEcdsaPublicKeyAsPemStr to get ECDSA public key as pem string
func ExportEcdsaPublicKeyAsPemStr(pubkey *ecdsa.PublicKey) (string, error) {
pubkeyBytes, err := x509.MarshalPKIXPublicKey(pubkey)
if err != nil {
return "", err
}
pubkeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "ECDSA PUBLIC KEY",
Bytes: pubkeyBytes,
},
)
return string(pubkeyPem), nil
}
// ParseEcdsaPrivateKeyFromPemStr to parse ECDSA private key from pem string
func ParseEcdsaPrivateKeyFromPemStr(privPEM string) (*ecdsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(privPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
priv, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return priv, nil
}
// ParseEcdsaPublicKeyFromPemStr to parse ECDSA public key from pem string
func ParseEcdsaPublicKeyFromPemStr(pubPEM string) (*ecdsa.PublicKey, error) {
block, _ := pem.Decode([]byte(pubPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
switch pub := pub.(type) {
case *ecdsa.PublicKey:
return pub, nil
default:
break // fall through
}
return nil, errors.New("Key type is not ECDSA")
}
// AsECDSAStr returns private, public key string or error
func AsECDSAStr(privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey) (string, string, error) {
// Export the keys to pem string
privPem, err := ExportEcdsaPrivateKeyAsPemStr(privateKey)
if err != nil {
return "", "", err
}
pubPem, err := ExportEcdsaPublicKeyAsPemStr(publicKey)
if err != nil {
return "", "", err
}
// Import the keys from pem string
privParsed, err := ParseEcdsaPrivateKeyFromPemStr(privPem)
if err != nil {
return "", "", err
}
pubParsed, err := ParseEcdsaPublicKeyFromPemStr(pubPem)
if err != nil {
return "", "", err
}
// Export the newly imported keys
privParsedPem, err := ExportEcdsaPrivateKeyAsPemStr(privParsed)
if err != nil {
return "", "", err
}
pubParsedPem, err := ExportEcdsaPublicKeyAsPemStr(pubParsed)
if err != nil {
return "", "", err
}
return privParsedPem, pubParsedPem, nil
}

19
server/crypto/hmac.go Normal file
View File

@ -0,0 +1,19 @@
package crypto
import "github.com/google/uuid"
// NewHMAC key returns new key that can be used to ecnrypt data using HMAC algo
func NewHMACKey() string {
key := uuid.New().String()
return key
}
// IsHMACValid checks if given string is valid HMCA algo
func IsHMACA(algo string) bool {
switch algo {
case "HS256", "HS384", "HS512":
return true
default:
return false
}
}

127
server/crypto/rsa.go Normal file
View File

@ -0,0 +1,127 @@
package crypto
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
)
// NewRSAKey to generate new RSA Key if env is not set
func NewRSAKey() (*rsa.PrivateKey, string, string, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, "", "", err
}
privateKey, publicKey, err := AsRSAStr(key, &key.PublicKey)
if err != nil {
return nil, "", "", err
}
return key, privateKey, publicKey, err
}
// IsRSA checks if given string is valid RSA algo
func IsRSA(algo string) bool {
switch algo {
case "RS256", "RS384", "RS512":
return true
default:
return false
}
}
// ExportRsaPrivateKeyAsPemStr to get RSA private key as pem string
func ExportRsaPrivateKeyAsPemStr(privkey *rsa.PrivateKey) string {
privkeyBytes := x509.MarshalPKCS1PrivateKey(privkey)
privkeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privkeyBytes,
},
)
return string(privkeyPem)
}
// ExportRsaPublicKeyAsPemStr to get RSA public key as pem string
func ExportRsaPublicKeyAsPemStr(pubkey *rsa.PublicKey) (string, error) {
pubkeyBytes, err := x509.MarshalPKIXPublicKey(pubkey)
if err != nil {
return "", err
}
pubkeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: pubkeyBytes,
},
)
return string(pubkeyPem), nil
}
// ParseRsaPrivateKeyFromPemStr to parse RSA private key from pem string
func ParseRsaPrivateKeyFromPemStr(privPEM string) (*rsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(privPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return priv, nil
}
// ParseRsaPublicKeyFromPemStr to parse RSA public key from pem string
func ParseRsaPublicKeyFromPemStr(pubPEM string) (*rsa.PublicKey, error) {
block, _ := pem.Decode([]byte(pubPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
switch pub := pub.(type) {
case *rsa.PublicKey:
return pub, nil
default:
break // fall through
}
return nil, errors.New("Key type is not RSA")
}
// AsRSAStr returns private, public key string or error
func AsRSAStr(privateKey *rsa.PrivateKey, publickKey *rsa.PublicKey) (string, string, error) {
// Export the keys to pem string
privPem := ExportRsaPrivateKeyAsPemStr(privateKey)
pubPem, err := ExportRsaPublicKeyAsPemStr(publickKey)
if err != nil {
return "", "", err
}
// Import the keys from pem string
privParsed, err := ParseRsaPrivateKeyFromPemStr(privPem)
if err != nil {
return "", "", err
}
pubParsed, err := ParseRsaPublicKeyFromPemStr(pubPem)
if err != nil {
return "", "", err
}
// Export the newly imported keys
privParsedPem := ExportRsaPrivateKeyAsPemStr(privParsed)
pubParsedPem, err := ExportRsaPublicKeyAsPemStr(pubParsed)
if err != nil {
return "", "", err
}
return privParsedPem, pubParsedPem, nil
}

205
server/env/env.go vendored
View File

@ -1,22 +1,80 @@
package env
import (
"fmt"
"log"
"os"
"strings"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/joho/godotenv"
)
// InitRequiredEnv to initialize EnvData and through error if required env are not present
func InitRequiredEnv() {
envPath := os.Getenv(constants.EnvKeyEnvPath)
if envPath == "" {
envPath = `.env`
}
if envstore.ARG_ENV_FILE != nil && *envstore.ARG_ENV_FILE != "" {
envPath = *envstore.ARG_ENV_FILE
}
err := godotenv.Load(envPath)
if err != nil {
log.Printf("using OS env instead of %s file", envPath)
}
dbURL := os.Getenv(constants.EnvKeyDatabaseURL)
dbType := os.Getenv(constants.EnvKeyDatabaseType)
dbName := os.Getenv(constants.EnvKeyDatabaseName)
if dbType == "" {
if envstore.ARG_DB_TYPE != nil && *envstore.ARG_DB_TYPE != "" {
dbType = *envstore.ARG_DB_TYPE
}
if dbType == "" {
panic("DATABASE_TYPE is required")
}
}
if dbURL == "" {
if envstore.ARG_DB_URL != nil && *envstore.ARG_DB_URL != "" {
dbURL = *envstore.ARG_DB_URL
}
if dbURL == "" {
panic("DATABASE_URL is required")
}
}
if dbName == "" {
if dbName == "" {
dbName = "authorizer"
}
}
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, envPath)
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL)
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType)
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseName, dbName)
}
// InitEnv to initialize EnvData and through error if required env are not present
func InitEnv() {
// get clone of current store
envData := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
func InitAllEnv() {
envData, err := GetEnvData()
if err != nil {
log.Println("No env data found in db, using local clone of env data")
// get clone of current store
envData = envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
}
if envData.StringEnv[constants.EnvKeyEnv] == "" {
envData.StringEnv[constants.EnvKeyEnv] = os.Getenv(constants.EnvKeyEnv)
@ -36,19 +94,6 @@ func InitEnv() {
envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL)
}
if envData.StringEnv[constants.EnvKeyEnvPath] == "" {
envData.StringEnv[constants.EnvKeyEnvPath] = `.env`
}
if envstore.ARG_ENV_FILE != nil && *envstore.ARG_ENV_FILE != "" {
envData.StringEnv[constants.EnvKeyEnvPath] = *envstore.ARG_ENV_FILE
}
err := godotenv.Load(envData.StringEnv[constants.EnvKeyEnvPath])
if err != nil {
log.Printf("using OS env instead of %s file", envData.StringEnv[constants.EnvKeyEnvPath])
}
if envData.StringEnv[constants.EnvKeyPort] == "" {
envData.StringEnv[constants.EnvKeyPort] = os.Getenv(constants.EnvKeyPort)
if envData.StringEnv[constants.EnvKeyPort] == "" {
@ -60,37 +105,6 @@ func InitEnv() {
envData.StringEnv[constants.EnvKeyAdminSecret] = os.Getenv(constants.EnvKeyAdminSecret)
}
if envData.StringEnv[constants.EnvKeyDatabaseType] == "" {
envData.StringEnv[constants.EnvKeyDatabaseType] = os.Getenv(constants.EnvKeyDatabaseType)
if envstore.ARG_DB_TYPE != nil && *envstore.ARG_DB_TYPE != "" {
envData.StringEnv[constants.EnvKeyDatabaseType] = *envstore.ARG_DB_TYPE
}
if envData.StringEnv[constants.EnvKeyDatabaseType] == "" {
panic("DATABASE_TYPE is required")
}
}
if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" {
envData.StringEnv[constants.EnvKeyDatabaseURL] = os.Getenv(constants.EnvKeyDatabaseURL)
if envstore.ARG_DB_URL != nil && *envstore.ARG_DB_URL != "" {
envData.StringEnv[constants.EnvKeyDatabaseURL] = *envstore.ARG_DB_URL
}
if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" {
panic("DATABASE_URL is required")
}
}
if envData.StringEnv[constants.EnvKeyDatabaseName] == "" {
envData.StringEnv[constants.EnvKeyDatabaseName] = os.Getenv(constants.EnvKeyDatabaseName)
if envData.StringEnv[constants.EnvKeyDatabaseName] == "" {
envData.StringEnv[constants.EnvKeyDatabaseName] = "authorizer"
}
}
if envData.StringEnv[constants.EnvKeySmtpHost] == "" {
envData.StringEnv[constants.EnvKeySmtpHost] = os.Getenv(constants.EnvKeySmtpHost)
}
@ -111,32 +125,83 @@ func InitEnv() {
envData.StringEnv[constants.EnvKeySenderEmail] = os.Getenv(constants.EnvKeySenderEmail)
}
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
envData.StringEnv[constants.EnvKeyJwtSecret] = os.Getenv(constants.EnvKeyJwtSecret)
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
envData.StringEnv[constants.EnvKeyJwtSecret] = uuid.New().String()
}
}
if envData.StringEnv[constants.EnvKeyCustomAccessTokenScript] == "" {
envData.StringEnv[constants.EnvKeyCustomAccessTokenScript] = os.Getenv(constants.EnvKeyCustomAccessTokenScript)
}
if envData.StringEnv[constants.EnvKeyJwtPrivateKey] == "" {
envData.StringEnv[constants.EnvKeyJwtPrivateKey] = os.Getenv(constants.EnvKeyJwtPrivateKey)
}
if envData.StringEnv[constants.EnvKeyJwtPublicKey] == "" {
envData.StringEnv[constants.EnvKeyJwtPublicKey] = os.Getenv(constants.EnvKeyJwtPublicKey)
}
algo := ""
if envData.StringEnv[constants.EnvKeyJwtType] == "" {
envData.StringEnv[constants.EnvKeyJwtType] = os.Getenv(constants.EnvKeyJwtType)
if envData.StringEnv[constants.EnvKeyJwtType] == "" {
envData.StringEnv[constants.EnvKeyJwtType] = "HS256"
envData.StringEnv[constants.EnvKeyJwtType] = "RS256"
algo = envData.StringEnv[constants.EnvKeyJwtType]
} else {
algo = envData.StringEnv[constants.EnvKeyJwtType]
if !crypto.IsHMACA(algo) && !crypto.IsRSA(algo) && !crypto.IsECDSA(algo) {
panic("JWT_TYPE is invalid")
}
}
}
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" && crypto.IsHMACA(algo) {
envData.StringEnv[constants.EnvKeyJwtSecret] = os.Getenv(constants.EnvKeyJwtSecret)
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
envData.StringEnv[constants.EnvKeyJwtSecret] = crypto.NewHMACKey()
}
}
if crypto.IsRSA(algo) || crypto.IsECDSA(algo) {
privateKey, publicKey := "", ""
if envData.StringEnv[constants.EnvKeyJwtPrivateKey] == "" {
privateKey = os.Getenv(constants.EnvKeyJwtPrivateKey)
}
if envData.StringEnv[constants.EnvKeyJwtPublicKey] == "" {
publicKey = os.Getenv(constants.EnvKeyJwtPublicKey)
}
// if algo is RSA / ECDSA, then we need to have both private and public key
// if either of them is not present generate new keys
if privateKey == "" || publicKey == "" {
if crypto.IsRSA(algo) {
_, privateKey, publicKey, err = crypto.NewRSAKey()
if err != nil {
panic(err)
}
} else if crypto.IsECDSA(algo) {
_, privateKey, publicKey, err = crypto.NewECDSAKey()
if err != nil {
panic(err)
}
}
} else {
// parse keys to make sure they are valid
if crypto.IsRSA(algo) {
_, err = crypto.ParseRsaPrivateKeyFromPemStr(privateKey)
if err != nil {
panic(err)
}
_, err = crypto.ParseRsaPublicKeyFromPemStr(publicKey)
if err != nil {
panic(err)
}
} else if crypto.IsECDSA(algo) {
_, err = crypto.ParseEcdsaPrivateKeyFromPemStr(privateKey)
if err != nil {
panic(err)
}
_, err = crypto.ParseEcdsaPublicKeyFromPemStr(publicKey)
if err != nil {
panic(err)
}
}
fmt.Println("=> keys parsed successfully")
}
fmt.Println(privateKey)
fmt.Println(publicKey)
envData.StringEnv[constants.EnvKeyJwtPrivateKey] = privateKey
envData.StringEnv[constants.EnvKeyJwtPublicKey] = publicKey
}
if envData.StringEnv[constants.EnvKeyJwtRoleClaim] == "" {
envData.StringEnv[constants.EnvKeyJwtRoleClaim] = os.Getenv(constants.EnvKeyJwtRoleClaim)
@ -145,6 +210,10 @@ func InitEnv() {
}
}
if envData.StringEnv[constants.EnvKeyCustomAccessTokenScript] == "" {
envData.StringEnv[constants.EnvKeyCustomAccessTokenScript] = os.Getenv(constants.EnvKeyCustomAccessTokenScript)
}
if envData.StringEnv[constants.EnvKeyRedisURL] == "" {
envData.StringEnv[constants.EnvKeyRedisURL] = os.Getenv(constants.EnvKeyRedisURL)
}

View File

@ -15,6 +15,40 @@ import (
"github.com/google/uuid"
)
// GetEnvData returns the env data from database
func GetEnvData() (envstore.Store, error) {
var result envstore.Store
env, err := db.Provider.GetEnv()
// config not found in db
if err != nil {
return result, err
}
encryptionKey := env.Hash
decryptedEncryptionKey, err := utils.DecryptB64(encryptionKey)
if err != nil {
return result, err
}
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
b64DecryptedConfig, err := utils.DecryptB64(env.EnvData)
if err != nil {
return result, err
}
decryptedConfigs, err := utils.DecryptAES([]byte(b64DecryptedConfig))
if err != nil {
return result, err
}
err = json.Unmarshal(decryptedConfigs, &result)
if err != nil {
return result, err
}
return result, err
}
// PersistEnv persists the environment variables to the database
func PersistEnv() error {
env, err := db.Provider.GetEnv()
@ -29,22 +63,16 @@ func PersistEnv() error {
if err != nil {
return err
}
// configData, err := json.Marshal()
// if err != nil {
// return err
// }
// encryptedConfig, err := utils.EncryptAES(configData)
// if err != nil {
// return err
// }
env = models.Env{
Hash: encodedHash,
EnvData: encryptedConfig,
}
db.Provider.AddEnv(env)
env, err = db.Provider.AddEnv(env)
if err != nil {
return err
}
} else {
// decrypt the config data from db
// decryption can be done using the hash stored in db
@ -134,6 +162,7 @@ func PersistEnv() error {
}
envstore.EnvInMemoryStoreObj.UpdateEnvStore(storeData)
if hasChanged {
encryptedConfig, err := utils.EncryptEnvData(storeData)
if err != nil {
@ -147,8 +176,11 @@ func PersistEnv() error {
return err
}
}
}
// ID of env is used to identify the config and declared as client id
// this client id can be used in `aud` section of JWT token
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyClientID, env.ID)
return nil
}

View File

@ -2,6 +2,7 @@ package main
import (
"flag"
"log"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
@ -22,13 +23,20 @@ func main() {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, VERSION)
env.InitEnv()
// initialize required envs (mainly db env & env file path)
env.InitRequiredEnv()
// initialize db provider
db.InitDB()
env.PersistEnv()
// initialize all envs
env.InitAllEnv()
// persist all envs
err := env.PersistEnv()
if err != nil {
log.Println("Error persisting env:", err)
}
sessionstore.InitSession()
oauth.InitOAuth()
router := routes.InitRouter()
router.Run(":" + envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyPort))

View File

@ -11,7 +11,7 @@ import (
func TestEnvs(t *testing.T) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample")
env.InitEnv()
env.InitAllEnv()
store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
assert.Equal(t, store.StringEnv[constants.EnvKeyEnv], "production")

View File

@ -78,7 +78,7 @@ func testSetup() TestSetup {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPassword, "test")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySenderEmail, "info@yopmail.com")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyProtectedRoles, []string{"admin"})
env.InitEnv()
env.InitAllEnv()
sessionstore.InitSession()
w := httptest.NewRecorder()