Skip to content

Commit

Permalink
Merge pull request #39 from ag5/feature/PLT-738
Browse files Browse the repository at this point in the history
Added backup command
  • Loading branch information
amohabir authored Oct 31, 2022
2 parents ff0b174 + 330731e commit 5a88f3b
Show file tree
Hide file tree
Showing 14 changed files with 772 additions and 118 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ release
.idea
.vscode
vendor
cmd/kiya/assets/
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changes

### v1.12.0

- Added backup option

### v1.11.4

- Fix an issue with azure client creation
Expand Down
149 changes: 120 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<img align="right" src="kea.jpg">

Kiya is a tool to manage secrets stored in any of:

- Google Secret Manager(GSM)
- Google Bucket and encrypted by Google Key Management Service (KMS)
- Amazon Web Services Parameter Store (SSM)
Expand All @@ -17,7 +18,8 @@ Examples are passwords, service accounts, TLS certificates, API tokens and Encry
managed with great care. This means secrets must be stored encrypted on reliable shared storage and its access must
controlled by AAA (authentication, authorisation and auditing).

**Kiya** is a simple tool that eases the access to the secrets stored in GSM,KMS or SSM. It requires an authenticated account and permissions for that account to read secrets and perform encryption and decryption.
**Kiya** is a simple tool that eases the access to the secrets stored in GSM,KMS or SSM. It requires an authenticated account and permissions for
that account to read secrets and perform encryption and decryption.

#### Labeled secrets

Expand All @@ -28,22 +30,26 @@ one parent key (lowercase with or without dots). The value must be a string whic
### Prerequisites

#### GCP
Kiya uses your authenticated Google account to access the Secret Manager / Storage Bucket, KMS and Audit Logging.

Kiya uses your authenticated Google account to access the Secret Manager / Storage Bucket, KMS and Audit Logging.
The bucket stores the encrypted secret value using the label as the storage key.

gcloud auth application-default login

#### AWS
Kiya uses your AWS credentials to access the AWS Parameter Store (part of Systems Management).

Kiya uses your AWS credentials to access the AWS Parameter Store (part of Systems Management).
All values are stored using the specified encryption key ID or the default key set for your AWS Account.

#### AKV

Kiya uses your authenticated default credentials. Make sure you have the Azure CLI installed.
All secrets are stored with the default config provided in your vault.

az login

#### File

When using the file backend, make sure Kiya is allowed to read and write to the provided location.
The file store is created with permission 0600

Expand All @@ -64,53 +70,58 @@ Use the backend `ssm` if you are storing keys in AWS Parameter Store as part of

```json
{
"teamF1-on-kms": {
"backend": "kms",
"projectID": "your-gcp-project",
"location": "global",
"keyring": "your-kiya-secrets-keyring",
"cryptoKey": "your-kiya-secrets-cryptokey",
"bucket": "your-kiya-secrets"
},
"teamF2-on-gsm": {
"backend": "gsm",
"projectID": "another-gcp-project"
},
"teamF3-on-file": {
"backend": "file",
"projectID": "my-file-name"
},
"teamF4-on-akv": {
"backend": "akv",
"vaultUrl": "https://<vault-name>.vault.azure.net"
},
"ag5": {
"backend": "ssm",
"location": "eu-central-1"
}
"teamF1-on-kms": {
"backend": "kms",
"projectID": "your-gcp-project",
"location": "global",
"keyring": "your-kiya-secrets-keyring",
"cryptoKey": "your-kiya-secrets-cryptokey",
"bucket": "your-kiya-secrets"
},
"teamF2-on-gsm": {
"backend": "gsm",
"projectID": "another-gcp-project"
},
"teamF3-on-file": {
"backend": "file",
"projectID": "my-file-name"
},
"teamF4-on-akv": {
"backend": "akv",
"vaultUrl": "https://<vault-name>.vault.azure.net"
},
"ag5": {
"backend": "ssm",
"location": "eu-central-1"
}
}

```

#### GCP

You should define `location`, `keyring`, `cryptoKey` and `bucket` for KMS based profiles.
For Google Secret Manager based profiles a `projectID` is sufficient.
For Google Secret Manager based profiles a `projectID` is sufficient.

#### AWS

You should define `location` for SSM (AWS Systems Management) based profiles ; its value is an AWS region.
The `cryptoKey` is optional and must be set if you do not want to use the default key setup for your AWS Account.

#### AKV

You should define the `vaultUrl` for AKV (Azure Key Vault) based profiles ; its value is the URI used to identify a vault on Azure.

#### File

You should define `projectID` as it is used as a prefix for the file name.
Optionally, you could provide `location` in order to store the file at a location of your choosing.

If no `location` is provided, $HOME/<projectID.secrets.kiya will be used.

When retrieving a password using **put** or **get**, provide the -pw my-master-password flag

Storing or remembering the master password is the responsibility of the user.
Storing or remembering the master password is the responsibility of the user.
You can use different master passwords for different keys.

For the best security, it is best not to store your master password on the same device as your store.
Expand Down Expand Up @@ -184,6 +195,84 @@ For accessing OS environment values:

kiya teamF1 move bitbucket.org/johndoe teamF2



## Backup

- You can create encrypted and unencrypted backups of your secrets.
- Store the public key in the same store or file system
- You can filter with `backup` command



| Arg | Type | Description |
| ---------------------------- | ------ | ------------------------------------------------------------ |
| `--encrypt-backup` | bool | *Default: **false*** if `true`, the backup will be encrypted and you need to specify the path to the public key using the `--backup-key` parameter |
| `--backup-key-store` | string | *Default: **file*** `file` - when your public key is stored on the file system or `store` - when your public key is stored in one of the cloud providers. |
| `--backup-key` | string | *Default: **./kiya_backupkey_rsa*** path to public key |
| `--backup-path` | string | *Default: **./kiya_backup*** path to backlup |
| `--backup-restore-overwrite` | bool | *Default: **false*** by default kiya will not override your keys, pass `true` at your own risk :) |
| | | |

### Backup without encryption

Backup all keys without encryption:

```shell
kiya teamF1 backup
```

or with params:

```shell
kiya --backup-path /nasdrive/backup/mybackup teamF1 backup "/my_keys/"
```
in this example the kiya backup only the keys containing `/my_keys/` and saves the backup to `/nasdrive/backup/mybackup`.


### Backup with encryption

```shell
kiya --backup-path /nasdrive/backup/mybackup --encrypt-backup --backup-key "./path/to/public_key" teamF! backup
```

...when the public key is stored in a vault:

```shell
kiya --backup-path /nasdrive/backup/mybackup --encrypt-backup --backup-key-store "store" --backup-key "/path/to/public_key" teamF! backup
```

### Restore non-encrypted backup

```shell
kiya --backup-path /nasdrive/backup/mybackup teamF1 restore
```

### Restore encrypted backup

```shell
kiya --backup-path /nasdrive/backup/mybackup --backup-key ./secure/path/backup_key ag5 restore
```

### Generate public/private key pair

```shell
kiya teamF1 keygen ./path/to/key/location
```
after executing this command you will get the result:

```shell
Key './path/to/key/location', './path/to/key/location_pub' saved
Public key copied to clipboard
```
The public key has been copied to the clipboard, but you must put the private key in a safe place (
e.g. print it out on paper and put it in a physical safe :)).

### Limitations

- In this version, the private key can only be retrieved from the file system
- The public key can now only be stored in the same profile

## Troubleshooting

### 1. Error
Expand All @@ -203,5 +292,7 @@ You do not have access to encrypted secrets from `some-bucket-name`.
&copy; 2017 kramphub.com. Apache License v2.

### 3. Error

message authentication failed

Make sure to run **put** or **get** with the -pw flag containing a master password.
1 change: 1 addition & 0 deletions backend/aws-parameterstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func (s *AWSParameterStore) Get(ctx context.Context, p *Profile, key string) ([]
if err != nil {
return []byte{}, err
}

return []byte(*output.Parameter.Value), nil
}

Expand Down
128 changes: 128 additions & 0 deletions cmd/kiya/cmd_backup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package main

import (
"context"
"crypto/rsa"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"os"

"github.com/kramphub/kiya/backend"
)

// Backup is a backup of all keys in store.
type Backup struct {
//Encrypted secret with public key and encoded as base64 string
Secret string `json:"secret"`
Encrypted bool `json:"encrypted"`
Data []byte `json:"data"`
}

// String returns a base64 String representation of the Backup.
func (b *Backup) String() string {
buf := encodeToJson(b)
return base64.URLEncoding.EncodeToString(buf)
}

// FromString returns a Backup from a string representation.
func (b *Backup) FromString(str string) {
buf, err := base64.URLEncoding.DecodeString(str)
if err != nil {
log.Fatalf("[FATAL] decode from string failed: %s", err.Error())
}

err = json.Unmarshal(buf, b)
if err != nil {
log.Fatalf("[FATAL] decode JSON string failed: %s", err.Error())
}
}

// SecretAsBytes returns the secret as bytes.
func (b *Backup) SecretAsBytes() []byte {
buf, err := base64.URLEncoding.DecodeString(b.Secret)
if err != nil {
log.Fatalf("[FATAL] decode secret base64 string failed: %s", err.Error())
}

return buf
}

// commandBackup creates a backup of all keys in store.
func commandBackup(ctx context.Context, b backend.Backend, target backend.Profile, filter string) (*Backup, error) {
items, err := getItems(ctx, b, target, filter)
if err != nil {
return nil, err
}

buf := encodeToJson(items)

return &Backup{Data: buf}, nil
}

// getItems returns all keys in store.
func getItems(ctx context.Context, b backend.Backend, target backend.Profile, filter string) (map[string][]byte, error) {
items := make(map[string][]byte)

keys := commandList(ctx, b, &target, filter)
totalKeys := len(keys)

for i, key := range keys {
buf, err := b.Get(ctx, &target, key.Name)
if err != nil {
fmt.Printf("error: get key '%s' failed, %s", key.Name, err.Error())
continue
}

items[key.Name] = buf
fmt.Printf("\rSaved keys: %d/%d", i+1, totalKeys)
}
fmt.Println()

return items, nil
}

// getPublicKey returns the public key from file or store.
func getPublicKey(ctx context.Context, b backend.Backend, target backend.Profile, location, key string) (*rsa.PublicKey, error) {
switch location {
case "store":
buf, err := b.Get(ctx, &target, key)
if err != nil {
return nil, fmt.Errorf("get public key '%s' failed, %w", key, err)
}

return exportPublicKeyFromPEMString(buf), nil
case "file":
fallthrough
default:
buf, err := os.ReadFile(key)
if err != nil {
return nil, fmt.Errorf("read public file '%s' failed, %w", key, err)
}

return exportPublicKeyFromPEMString(buf), nil
}
}

// getPrivateKey returns the private key from file.
func getPrivateKey(ctx context.Context, b backend.Backend, target backend.Profile, location, key string) (*rsa.PrivateKey, error) {
switch location {
case "store":
buf, err := b.Get(ctx, &target, key)
if err != nil {
return nil, fmt.Errorf("get private key '%s' failed, %w", key, err)
}

return exportPrivateKeyFromPEMString(buf), nil
case "file":
fallthrough
default:
buf, err := os.ReadFile(key)
if err != nil {
return nil, fmt.Errorf("read private file '%s' failed, %w", key, err)
}

return exportPrivateKeyFromPEMString(buf), nil
}
}
Loading

0 comments on commit 5a88f3b

Please sign in to comment.