Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for encryption at rest when using offline mode #423

Closed
sochoaAtMulesoft opened this issue Aug 1, 2024 · 1 comment
Closed

Support for encryption at rest when using offline mode #423

sochoaAtMulesoft opened this issue Aug 1, 2024 · 1 comment

Comments

@sochoaAtMulesoft
Copy link

Is your feature request related to a problem? Please describe.

Our organization requires data to be encrypted at rest to comply with security policies and industry regulations. While the LaunchDarkly Relay Proxy supports encryption in transit using FIPS 140-2 validated encryption modules, it does not currently offer encryption at rest for data stored in offline mode. This creates a compliance gap and potential security risk for our sensitive data.

Describe the solution you'd like

We would like the LaunchDarkly Relay Proxy to support encryption at rest for data stored on disk when running in offline mode. This feature should ensure that all data, including feature flags and user attributes, are encrypted using strong encryption algorithms when stored locally by the Relay Proxy.

Describe alternatives you've considered

Implementing additional infrastructure measures such as encrypted filesystems or using external storage solutions that provide encryption at rest. However, these alternatives introduce additional complexity and may not fully integrate with the Relay Proxy's operations.

Using other feature management tools that provide comprehensive encryption capabilities, but this would require significant changes to our current setup and might not offer the same level of functionality and integration as LaunchDarkly.

Additional context

This feature is critical for organizations that need to comply with stringent security standards and regulations, such as those in the finance, healthcare, and government sectors. Adding encryption at rest support would significantly enhance the security posture of the LaunchDarkly Relay Proxy and make it a more attractive solution for security-conscious customers.

@sochoaAtMulesoft sochoaAtMulesoft changed the title Support for encryption at rest Support for encryption at rest when using offline mode Aug 1, 2024
@sochoaAtMulesoft
Copy link
Author

Would be awesome to support AES-256 and RSA encrypt/decrypt.

Suggested env vars

export LD_RELAY_ENCRYPTION_ENABLED=true
export LD_RELAY_ENCRYPTION_METHOD=AES-256 # Options: AES-256 or RSA
export LD_RELAY_ENCRYPTION_KEY_BASE64=<your-base64-encoded-encryption-key>

Suggested encryptor interface

type Encryptor interface {
    Encrypt(data []byte) ([]byte, error)
    Decrypt(data []byte) ([]byte, error)
}

Suggested AES-256 implementation

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/base64"
    "errors"
    "io"
)

// Aes256Encryptor implements the Encryptor interface for AES-256 encryption
type Aes256Encryptor struct {
    key []byte
}

// NewAes256Encryptor creates a new instance of Aes256Encryptor
func NewAes256Encryptor(base64Key string) (*Aes256Encryptor, error) {
    key, err := base64.StdEncoding.DecodeString(base64Key)
    if err != nil {
        return nil, errors.New("invalid base64 key")
    }
    if len(key) != 32 {
        return nil, errors.New("key length must be 32 bytes")
    }
    return &Aes256Encryptor{key: key}, nil
}

// Encrypt encrypts the given data using AES-256
func (e *Aes256Encryptor) Encrypt(data []byte) ([]byte, error) {
    block, err := aes.NewCipher(e.key)
    if err != nil {
        return nil, err
    }

    ciphertext := make([]byte, aes.BlockSize+len(data))
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return nil, err
    }

    stream := cipher.NewCFBEncrypter(block, iv)
    stream.XORKeyStream(ciphertext[aes.BlockSize:], data)

    return ciphertext, nil
}

// Decrypt decrypts the given data using AES-256
func (e *Aes256Encryptor) Decrypt(data []byte) ([]byte, error) {
    block, err := aes.NewCipher(e.key)
    if err != nil {
        return nil, err
    }

    if len(data) < aes.BlockSize {
        return nil, errors.New("ciphertext too short")
    }

    iv := data[:aes.BlockSize]
    ciphertext := data[aes.BlockSize:]

    stream := cipher.NewCFBDecrypter(block, iv)
    stream.XORKeyStream(ciphertext, ciphertext)

    return ciphertext, nil
}

Suggested RSA encryptor impelementation

import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/sha256"
    "encoding/base64"
    "errors"
)

// RsaEncryptor implements the Encryptor interface for RSA encryption
type RsaEncryptor struct {
    privateKey *rsa.PrivateKey
    publicKey  *rsa.PublicKey
}

// NewRsaEncryptor creates a new instance of RsaEncryptor
func NewRsaEncryptor(base64PrivateKey string, base64PublicKey string) (*RsaEncryptor, error) {
    privateKeyBytes, err := base64.StdEncoding.DecodeString(base64PrivateKey)
    if err != nil {
        return nil, errors.New("invalid base64 private key")
    }

    publicKeyBytes, err := base64.StdEncoding.DecodeString(base64PublicKey)
    if err != nil {
        return nil, errors.New("invalid base64 public key")
    }

    privateKey, err := rsa.ParsePKCS1PrivateKey(privateKeyBytes)
    if err != nil {
        return nil, err
    }

    publicKey, err := x509.ParsePKCS1PublicKey(publicKeyBytes)
    if err != nil {
        return nil, err
    }

    return &RsaEncryptor{privateKey: privateKey, publicKey: publicKey}, nil
}

// Encrypt encrypts the given data using RSA
func (e *RsaEncryptor) Encrypt(data []byte) ([]byte, error) {
    ciphertext, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, e.publicKey, data, nil)
    if err != nil {
        return nil, err
    }
    return ciphertext, nil
}

// Decrypt decrypts the given data using RSA
func (e *RsaEncryptor) Decrypt(data []byte) ([]byte, error) {
    plaintext, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, e.privateKey, data, nil)
    if err != nil {
        return nil, err
    }
    return plaintext, nil
}

Suggested encryptor factory based on encryption method

import (
    "errors"
    "os"
)

// EncryptionMethod represents the encryption method to use
type EncryptionMethod string

const (
    AES256         EncryptionMethod = "AES-256"
    RSA            EncryptionMethod = "RSA"
    ChaCha20Poly1305 EncryptionMethod = "ChaCha20-Poly1305"
)

// NewEncryptor creates an Encryptor based on the specified method and environment variables
func NewEncryptor(method EncryptionMethod) (Encryptor, error) {
    switch method {
    case AES256:
        key := os.Getenv("LD_RELAY_ENCRYPTION_KEY_BASE64")
        return NewAes256Encryptor(key)
    case RSA:
        privateKey := os.Getenv("LD_RELAY_ENCRYPTION_PRIVATE_KEY_BASE64")
        publicKey := os.Getenv("LD_RELAY_ENCRYPTION_PUBLIC_KEY_BASE64")
        return NewRsaEncryptor(privateKey, publicKey)
    case ChaCha20Poly1305:
        key := os.Getenv("LD_RELAY_ENCRYPTION_KEY_BASE64")
        return NewChaCha20Poly1305Encryptor(key)
    default:
        return nil, errors.New("unsupported encryption method")
    }
}

@sochoaAtMulesoft sochoaAtMulesoft closed this as not planned Won't fix, can't repro, duplicate, stale Aug 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant