-
Notifications
You must be signed in to change notification settings - Fork 559
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
- Loading branch information
Showing
5 changed files
with
266 additions
and
17 deletions.
There are no files selected for viewing
This file contains 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,123 @@ | ||
package keychain | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
|
||
"go.mozilla.org/sops/v3" | ||
"go.mozilla.org/sops/v3/aes" | ||
sopsage "go.mozilla.org/sops/v3/age" | ||
"go.mozilla.org/sops/v3/cmd/sops/common" | ||
"go.mozilla.org/sops/v3/cmd/sops/formats" | ||
sopsdecrypt "go.mozilla.org/sops/v3/decrypt" | ||
sopskeys "go.mozilla.org/sops/v3/keys" | ||
sopsyaml "go.mozilla.org/sops/v3/stores/yaml" | ||
"go.mozilla.org/sops/v3/version" | ||
) | ||
|
||
// setupEnv: hack to inject a SOPS env var for age | ||
func setupEnv() error { | ||
p, err := Path() | ||
if err != nil { | ||
return err | ||
} | ||
return os.Setenv("SOPS_AGE_KEY_FILE", p) | ||
} | ||
|
||
// Encrypt data using SOPS with the AGE backend, using the provided public key | ||
func Encrypt(ctx context.Context, path string, plaintext []byte, key string) ([]byte, error) { | ||
if err := setupEnv(); err != nil { | ||
return nil, err | ||
} | ||
|
||
store := &sopsyaml.Store{} | ||
branches, err := store.LoadPlainFile(plaintext) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
ageKeys, err := sopsage.MasterKeysFromRecipients(key) | ||
if err != nil { | ||
return nil, err | ||
} | ||
ageMasterKeys := make([]sopskeys.MasterKey, 0, len(ageKeys)) | ||
for _, k := range ageKeys { | ||
ageMasterKeys = append(ageMasterKeys, k) | ||
} | ||
var group sops.KeyGroup | ||
group = append(group, ageMasterKeys...) | ||
|
||
tree := sops.Tree{ | ||
Branches: branches, | ||
Metadata: sops.Metadata{ | ||
KeyGroups: []sops.KeyGroup{group}, | ||
EncryptedSuffix: "secret", | ||
Version: version.Version, | ||
}, | ||
FilePath: path, | ||
} | ||
|
||
// Generate a data key | ||
dataKey, errs := tree.GenerateDataKey() | ||
if len(errs) > 0 { | ||
return nil, fmt.Errorf("error encrypting the data key with one or more master keys: %v", errs) | ||
} | ||
|
||
err = common.EncryptTree(common.EncryptTreeOpts{ | ||
DataKey: dataKey, Tree: &tree, Cipher: aes.NewCipher(), | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return store.EmitEncryptedFile(tree) | ||
} | ||
|
||
// Reencrypt a file with new content using the same keys | ||
func Reencrypt(_ context.Context, path string, plaintext []byte) ([]byte, error) { | ||
if err := setupEnv(); err != nil { | ||
return nil, err | ||
} | ||
|
||
current, err := os.ReadFile(path) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Load the encrypted file | ||
store := &sopsyaml.Store{} | ||
tree, err := store.LoadEncryptedFile(current) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Update the file with the new data | ||
newBranches, err := store.LoadPlainFile(plaintext) | ||
if err != nil { | ||
return nil, err | ||
} | ||
tree.Branches = newBranches | ||
|
||
// Re-encrypt the file | ||
key, err := tree.Metadata.GetDataKey() | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = common.EncryptTree(common.EncryptTreeOpts{ | ||
DataKey: key, Tree: &tree, Cipher: aes.NewCipher(), | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return store.EmitEncryptedFile(tree) | ||
} | ||
|
||
// Decrypt data using sops | ||
func Decrypt(_ context.Context, encrypted []byte) ([]byte, error) { | ||
if err := setupEnv(); err != nil { | ||
return nil, err | ||
} | ||
|
||
return sopsdecrypt.DataWithFormat(encrypted, formats.Yaml) | ||
} |
This file contains 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,96 @@ | ||
package keychain | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"os/user" | ||
"path" | ||
"path/filepath" | ||
"time" | ||
|
||
"filippo.io/age" | ||
"github.com/rs/zerolog/log" | ||
) | ||
|
||
func Path() (string, error) { | ||
usr, err := user.Current() | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return path.Join(usr.HomeDir, ".dagger", "keys.txt"), nil | ||
} | ||
|
||
func Default(ctx context.Context) (string, error) { | ||
keys, err := List(ctx) | ||
if err != nil { | ||
if errors.Is(err, os.ErrNotExist) { | ||
return Generate(ctx) | ||
} | ||
return "", err | ||
} | ||
if len(keys) == 0 { | ||
return "", errors.New("no identities found in the keys file") | ||
} | ||
|
||
return keys[0].Recipient().String(), nil | ||
} | ||
|
||
func Generate(ctx context.Context) (string, error) { | ||
keysFile, err := Path() | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
k, err := age.GenerateX25519Identity() | ||
if err != nil { | ||
return "", fmt.Errorf("internal error: %v", err) | ||
} | ||
|
||
if err := os.MkdirAll(filepath.Dir(keysFile), 0755); err != nil { | ||
return "", err | ||
} | ||
f, err := os.OpenFile(keysFile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to open keys file %q: %v", keysFile, err) | ||
} | ||
defer f.Close() | ||
fmt.Fprintf(f, "# created: %s\n", time.Now().Format(time.RFC3339)) | ||
fmt.Fprintf(f, "# public key: %s\n", k.Recipient()) | ||
fmt.Fprintf(f, "%s\n", k) | ||
|
||
pubkey := k.Recipient().String() | ||
|
||
log.Ctx(ctx).Debug().Str("publicKey", pubkey).Msg("generating keypair") | ||
|
||
return pubkey, nil | ||
} | ||
|
||
func List(ctx context.Context) ([]*age.X25519Identity, error) { | ||
keysFile, err := Path() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
f, err := os.Open(keysFile) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to open keys file file %q: %w", keysFile, err) | ||
} | ||
ids, err := age.ParseIdentities(f) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse input: %w", err) | ||
} | ||
|
||
keys := make([]*age.X25519Identity, 0, len(ids)) | ||
for _, id := range ids { | ||
key, ok := ids[0].(*age.X25519Identity) | ||
if !ok { | ||
return nil, fmt.Errorf("internal error: unexpected identity type: %T", id) | ||
} | ||
keys = append(keys, key) | ||
} | ||
|
||
return keys, nil | ||
} |
This file contains 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 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 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