Remove keychain
Signed-off-by: Joel Longtine <joel@dagger.io>
This commit is contained in:
parent
55bf5ff4dd
commit
7e86b00962
@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"go.dagger.io/dagger/cmd/dagger/cmd/mod"
|
"go.dagger.io/dagger/cmd/dagger/cmd/mod"
|
||||||
"go.dagger.io/dagger/cmd/dagger/logger"
|
"go.dagger.io/dagger/cmd/dagger/logger"
|
||||||
"go.dagger.io/dagger/keychain"
|
|
||||||
|
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
@ -34,15 +33,7 @@ func init() {
|
|||||||
rootCmd.PersistentFlags().String("project", "", "Specify a project directory (defaults to current)")
|
rootCmd.PersistentFlags().String("project", "", "Specify a project directory (defaults to current)")
|
||||||
|
|
||||||
rootCmd.PersistentPreRun = func(cmd *cobra.Command, _ []string) {
|
rootCmd.PersistentPreRun = func(cmd *cobra.Command, _ []string) {
|
||||||
lg := logger.New()
|
|
||||||
ctx := lg.WithContext(cmd.Context())
|
|
||||||
|
|
||||||
go checkVersion()
|
go checkVersion()
|
||||||
|
|
||||||
err := keychain.EnsureDefaultKey(ctx)
|
|
||||||
if err != nil {
|
|
||||||
lg.Fatal().Err(err).Msg("failed to generate default key")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
rootCmd.PersistentPostRun = func(*cobra.Command, []string) {
|
rootCmd.PersistentPostRun = func(*cobra.Command, []string) {
|
||||||
warnVersion()
|
warnVersion()
|
||||||
|
@ -1,162 +0,0 @@
|
|||||||
package keychain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.mozilla.org/sops/v3"
|
|
||||||
sopsaes "go.mozilla.org/sops/v3/aes"
|
|
||||||
sopsage "go.mozilla.org/sops/v3/age"
|
|
||||||
"go.mozilla.org/sops/v3/cmd/sops/common"
|
|
||||||
sopskeys "go.mozilla.org/sops/v3/keys"
|
|
||||||
sopsyaml "go.mozilla.org/sops/v3/stores/yaml"
|
|
||||||
"go.mozilla.org/sops/v3/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
cipher = sopsaes.NewCipher()
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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: cipher,
|
|
||||||
})
|
|
||||||
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: cipher,
|
|
||||||
})
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
store := &sopsyaml.Store{}
|
|
||||||
|
|
||||||
// Load SOPS file and access the data key
|
|
||||||
tree, err := store.LoadEncryptedFile(encrypted)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
key, err := tree.Metadata.GetDataKey()
|
|
||||||
if err != nil {
|
|
||||||
if userErr, ok := err.(sops.UserError); ok {
|
|
||||||
err = fmt.Errorf(userErr.UserError())
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt the tree
|
|
||||||
mac, err := tree.Decrypt(key, cipher)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the hash of the cleartext tree and compare it with
|
|
||||||
// the one that was stored in the document. If they match,
|
|
||||||
// integrity was preserved
|
|
||||||
originalMac, err := cipher.Decrypt(
|
|
||||||
tree.Metadata.MessageAuthenticationCode,
|
|
||||||
key,
|
|
||||||
tree.Metadata.LastModified.Format(time.RFC3339),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if originalMac != mac {
|
|
||||||
return nil, fmt.Errorf("failed to verify data integrity. expected mac %q, got %q", originalMac, mac)
|
|
||||||
}
|
|
||||||
|
|
||||||
return store.EmitPlainFile(tree.Branches)
|
|
||||||
}
|
|
140
keychain/keys.go
140
keychain/keys.go
@ -1,140 +0,0 @@
|
|||||||
package keychain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"filippo.io/age"
|
|
||||||
"github.com/mitchellh/go-homedir"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Path() (string, error) {
|
|
||||||
return homedir.Expand("~/.config/dagger/keys.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
func EnsureDefaultKey(ctx context.Context) error {
|
|
||||||
keysFile, err := Path()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the keys file already exists, there's nothing to do.
|
|
||||||
_, err = os.Stat(keysFile)
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we got a different error than not existent, abort
|
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt a migration from the old keys file
|
|
||||||
migrated, err := migrateKeys(ctx, keysFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we migrated a previous identity, stop here.
|
|
||||||
if migrated {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, generate a new key
|
|
||||||
log.Ctx(ctx).Debug().Msg("generating default key pair")
|
|
||||||
_, err = Generate(ctx)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// migrateKeys attempts a migration from `~/.dagger/keys.txt` to `~/.config/dagger/keys.txt`
|
|
||||||
func migrateKeys(ctx context.Context, keysFile string) (bool, error) {
|
|
||||||
oldKeysFile, err := homedir.Expand("~/.dagger/keys.txt")
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(oldKeysFile); err != nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(keysFile), 0700); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Ctx(ctx).Info().Msg("migrating keychain")
|
|
||||||
return true, os.Rename(oldKeysFile, keysFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Default(ctx context.Context) (string, error) {
|
|
||||||
keys, err := List(ctx)
|
|
||||||
if err != nil {
|
|
||||||
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), 0700); 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("keypair generated")
|
|
||||||
|
|
||||||
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 %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 := id.(*age.X25519Identity)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("internal error: unexpected identity type: %T", id)
|
|
||||||
}
|
|
||||||
keys = append(keys, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return keys, nil
|
|
||||||
}
|
|
Reference in New Issue
Block a user