/age-encryption-engine

A library to utilize age in your project to encrypt secrets

Primary LanguageGo

Age Encryption Engine

Sponsored by Lendiom

Usage

Expects at least 3 age public keys to be provided and 1 private key. The public key to that private key must be in the list of public keys.

On startup it will call a function to check that all secrets can be decrypted with the provided private key.

Generating Keys

Visit the age project and download the age cli.

❯ age-keygen
# created: 2023-02-13T14:07:43-06:00
# public key: age1ytvwh068w6qcaflq9cld2ag8rf3482da08xnmgz67nd0vezwwflqeyhwpe
AGE-SECRET-KEY-12YZRS0YPKYGKR0FK859QCU3DKP5CQZUKCK24F62E565WWDQDQ6RSSQLT2Y

Do that two more times. Grab one of the private keys and put the other in a safe place.

Setup

package main

import (
	"fmt"

	aee "github.com/geekgonecrazy/age-encryption-engine"
)

func main() {
	publicKeys := os.Getenv("AEE_PUBLIC_KEYS")
	privateKey := os.Getenv("AEE_PRIVATE_KEY")

	if len(publicKeys) < 1 {
		panic("AEE_PUBLIC_KEYS environment variable containing publicKeys is required to start")
	}

	if len(privateKey) < 1 {
		panic("AEE_PRIVATE_KEY environment variable containing encryption key is required to start")
	}

	engine := aee.New()

	if err := engine.AddEncryptionKeys(strings.Split(publicKeys, ",")); err != nil {
		panic(err)
	}

	if err := engine.AddDecryptionKey(privateKey); err != nil {
		panic(err)
	}

	engine.RegisterAuditHandler(func(action aee.SecretAuditAction) error {
		// Replace with your own logic to save audit log
		fmt.Printf("performer:%s\naction:%s\nkey:%s", action.Performer, action.Action, action.Key)

		return nil
	})

	engine.RegisterSaveHandler(func(action aee.Secret) error {
		// Logic to write your secret to persistant storage

		return nil
	})

	engine.RegisterReadHandler(func(key string) (aee.Secret, error) {
		// Logic to read secret from persistant storage

		return value, nil
	})

	engine.RegisterAreThereUnDecryptableSecretsHandler(func(publicKey string) (bool, error) {
		// The engine takes the private key and gets its public key.  Then calls this function giving you a chance to find any secrets that won't be able to be decrypted by this key.  See test suite for an example of how this works using in memory db

		return false, nil
	})

	if err := engine.Start(); err != nil {
		// handle - common cases would be not enough keys or no matching public key for your private key
	}
}

This case assumes you'd start up with environment variables something like:

AEE_PUBLIC_KEYS=age1ytvwh068w6qcaflq9cld2ag8rf3482da08xnmgz67nd0vezwwflqeyhwpe,age1g5985y3242h3lwsq6f044324a0dgd2ss3w2ymmdq0gwr2359a5qsvd3dm2,age1320sl3g4jhrhs22gd3gy386pss3jxkr97g4sn4pmrtzjkdp8r98q5gxhkn
AEE_PRIVATE_KEY=AGE-SECRET-KEY-1WDLSS8XJD0PSG9GUNUGHASA7TET0PM6RUCQEUEZ0RC95VYH2KJWQD0A46Y

StoreSecret

if err := engine.StoreSecret("func-updateSecret", "organization/{id}/customer/{id}/secret", "super-secret-words-here"); err != nil {
    // Handle your error
}

RetrieveSecret

decryptedResult, err := engine.Retrieve("func-readSecret", "organization/{id}/customer/{id}/secret")
if err != nil {
    // Handle your error
}

// Do something carefully with your secret

GetArmored Secret

Not sure about keeping this one exposed

armoredResult, err = engine.GetArmoredSecret("test-armoring", "organization/{id}/customer/{id}/secret")
if err != nil {
    // Handle your error
}

// do with your armored secret as you need

Auditing

Every function that touches a secret takes a performer as the first argument. This could be used maybe to do something like this:

func auditPerformerHelper(detail string, userId string, ip string) string {
    return fmt.Sprintf("%s-%s-%s", detail, userId, ip)
}

func retrieveMySecret(secretName string) (string, error) {
    userId := 123 // Pull from your session
    ip := "8.8.8.8" // Pull from your request

    decryptedResult, err := engine.DecryptSecret(auditPerformerHelper(userId, ip, "retrieve-my-secret"), fmt.Sprintf("genericSecret/%s/%s", userId, secretName))
    if err != nil {
        return "", err
    }

    return decryptedResult, nil
}

Now in your audit trail you can know what secret was accessed via what method and by who.