-
Notifications
You must be signed in to change notification settings - Fork 1
feat(store): encrypted filestore #29
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
Merged
Changes from 20 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
503c239
chore(store): export common secret factory type
Benehiko 4f60a61
chore: add filippo.io/age package
Benehiko 27589b8
feat(store): posix compliant store using age encryption
Benehiko b3fb5d0
test: posixage package
Benehiko 32065de
posixage: add README
Benehiko d5674db
posixage: test helper funcs
Benehiko aea04e8
posixage: more store tests
Benehiko af36409
posixage: options should return error
Benehiko 20f6e59
posixage: lock recovery pattern
Benehiko 4b381fa
chore: fix lint
Benehiko 099a56f
test: properly close os.Root
Benehiko 77e1aaa
posixage: simplify locking the store
Benehiko ce8c991
posixage: use flock
Benehiko 2c98617
posixage: deduplicate code
Benehiko b3501a1
posixage: refactor and move helpers
Benehiko 2270568
posixage: refactor store
Benehiko 285ca9d
posixage: group encryption/decryption prompt funcs under one type
Benehiko 0abb842
chore: update godoc
Benehiko 6e0deaa
chore: update mod
Benehiko fea1dea
chore: fix lint
Benehiko ae46908
posixage: always unlock mutex on flock error
Benehiko dcf0f86
chore: better code comments
Benehiko 0538de1
posixage(test): add testLogger
Benehiko 030c3de
chore: fix spelling errors
Benehiko 180b7a1
posixage: set the error correctly for the defer
Benehiko File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| # Store posixage | ||
|
|
||
| The posixage store is a POSIX compliant encrypted file store. It uses [age](https://github.com/filoSottile/age) | ||
| to encrypt/decrypt its files and has support for password, ssh and age keys. | ||
|
|
||
| ## Quickstart | ||
|
|
||
| ```go | ||
| import "github.com/docker/secrets-engine/store/posixage" | ||
|
|
||
| func main() { | ||
| root, err := os.OpenRoot("my/secrets/path") | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
|
|
||
| s, err := posixage.New(root, | ||
| func() *mocks.MockCredential { | ||
| return &mocks.MockCredential{} | ||
| }, | ||
| WithEncryptionCallbackFunc[EncryptionPassword](func(_ context.Context) ([]byte, error) { | ||
| return []byte(masterKey), nil | ||
| }), | ||
| WithDecryptionCallbackFunc[DecryptionPassword](func(_ context.Context) ([]byte, error) { | ||
| return []byte(masterKey), nil | ||
| }), | ||
| ) | ||
| } | ||
| ``` | ||
|
|
||
| The store allows you to register multiple encryption and decryption callback | ||
| functions. Each callback gives your application control over how to retrieve | ||
| the required data — for example, from environment variables, a configuration | ||
| file, or via an interactive user prompt. | ||
|
|
||
| ### Features | ||
|
|
||
| - Support for multiple encryption functions | ||
| - Support for multiple decryption functions | ||
|
|
||
| Callbacks are invoked in the order they are registered. For decryption, the | ||
| store tries each callback in sequence, and the first one that successfully | ||
| provides a valid key will return the decrypted secret. | ||
|
|
||
| Here's an example of accepting multiple passwords for encryption: | ||
|
|
||
| ```go | ||
| import "github.com/docker/secrets-engine/store/posixage" | ||
|
|
||
| func main() { | ||
| root, err := os.OpenRoot("my/secrets/path") | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
|
|
||
| s, err := posixage.New(root, | ||
| func() *mocks.MockCredential { | ||
| return &mocks.MockCredential{} | ||
| }, | ||
| WithEncryptionCallbackFunc[EncryptionPassword](func(_ context.Context) ([]byte, error) { | ||
| return []byte(masterKey), nil | ||
| }), | ||
| WithEncryptionCallbackFunc[EncryptionPassword](func(_ context.Context) ([]byte, error) { | ||
| return []byte(bobPassword), nil | ||
| }), | ||
| WithEncryptionCallbackFunc[EncryptionAgeX25519](func(_ context.Context) ([]byte, error) { | ||
| return []byte(identity.Recipient().String()), nil | ||
| }), | ||
| WithDecryptionCallbackFunc[DecryptionPassword](func(_ context.Context) ([]byte, error) { | ||
| return []byte(masterKey), nil | ||
| }), | ||
| ) | ||
| } | ||
| ``` | ||
|
|
||
| ### Secrets | ||
|
|
||
| Any secret format is supported as long as it conforms to the `store.Secret` interface. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| package secretfile | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
|
|
||
| "filippo.io/age" | ||
| "filippo.io/age/agessh" | ||
| ) | ||
|
|
||
| type ( | ||
| // PromptFunc is a callback invoked by the store when encrypting or | ||
| // decrypting a file. The function is expected to return the key material | ||
| // (as a byte slice) or an error if the key cannot be obtained. | ||
| PromptFunc func(context.Context) ([]byte, error) | ||
|
|
||
| // KeyType identifies the type of encryption or decryption key associated | ||
| // with a secret (e.g., password, age, or SSH). | ||
| KeyType string | ||
| ) | ||
|
|
||
| const ( | ||
| PasswordKeyType KeyType = "pass" | ||
| AgeKeyType KeyType = "age" | ||
| SSHKeyType KeyType = "ssh" | ||
| ) | ||
|
|
||
| func getRecipient(k KeyType, encryptionKey string) (age.Recipient, error) { | ||
| var recipient age.Recipient | ||
| var err error | ||
|
|
||
| switch k { | ||
| case PasswordKeyType: | ||
| recipient, err = age.NewScryptRecipient(encryptionKey) | ||
| case AgeKeyType: | ||
| recipient, err = age.ParseX25519Recipient(encryptionKey) | ||
| case SSHKeyType: | ||
| recipient, err = agessh.ParseRecipient(encryptionKey) | ||
| default: | ||
| return nil, fmt.Errorf("unsupported encryption type %T", k) | ||
| } | ||
|
|
||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return recipient, nil | ||
| } | ||
|
|
||
| // GetRecipients returns a slice of [age.Recipient] for the given key type and | ||
| // encryption keys. | ||
| // | ||
| // The recipient implementation depends on the provided [KeyType]: | ||
| // - passwordKeyType → [age.NewScryptRecipient] | ||
| // - ageKeyType → [age.ParseX25519Recipient] | ||
| // - sshKeyType → [agessh.ParseRecipient] | ||
| // | ||
| // An error is returned if the key cannot be parsed or the key type is | ||
| // unsupported. | ||
| func GetRecipients(k KeyType, encryptionKeys []string) ([]age.Recipient, error) { | ||
| var recipients []age.Recipient | ||
| for _, encryptionKey := range encryptionKeys { | ||
| recipient, err := getRecipient(k, encryptionKey) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| recipients = append(recipients, recipient) | ||
| } | ||
| return recipients, nil | ||
| } | ||
|
|
||
| // GetIdentity returns an [age.Identity] for the given key type and | ||
| // decryption key. | ||
| // | ||
| // The identity implementation depends on the provided [KeyType]: | ||
| // - PasswordKeyType → [age.NewScryptIdentity] | ||
| // - AgeKeyType → [age.ParseX25519Identity] | ||
| // - SSHKeyType → [agessh.ParseIdentity] | ||
| // | ||
| // An error is returned if the key cannot be parsed or the key type is | ||
| // unsupported. | ||
| func GetIdentity(k KeyType, decryptionKey string) (age.Identity, error) { | ||
| var identity age.Identity | ||
| var err error | ||
|
|
||
| switch k { | ||
| case PasswordKeyType: | ||
| identity, err = age.NewScryptIdentity(decryptionKey) | ||
| case AgeKeyType: | ||
| identity, err = age.ParseX25519Identity(decryptionKey) | ||
| case SSHKeyType: | ||
| identity, err = agessh.ParseIdentity([]byte(decryptionKey)) | ||
| default: | ||
| return nil, fmt.Errorf("unsupported decryption type %T", k) | ||
| } | ||
|
|
||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return identity, nil | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.