Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
71d3492
WIP: tdf encrypt/decrypt
jakedoublev Apr 9, 2024
bdcc11c
Merge branch 'main' into feat/tdf-demo
jakedoublev Apr 10, 2024
c5115f9
use new docs driven cli paradigm
jakedoublev Apr 10, 2024
695996c
fix error handler ignoring message string
jakedoublev Apr 10, 2024
6c7739f
ignore any TDFs created by the CLI
jakedoublev Apr 10, 2024
16fee7d
use latest platform
jakedoublev Apr 10, 2024
6b293da
checkpoint with working demo of encrypt/decrypt
jakedoublev Apr 11, 2024
95fd752
use bytes instead of string in decrypt to support complex file types
jakedoublev Apr 11, 2024
24cdc20
group tdf commands in CLI
jakedoublev Apr 11, 2024
b4f15fc
fix /Users/jake.vanvorhis docs
jakedoublev Apr 12, 2024
02ff2ce
pass values into TDF encrypt flow
jakedoublev Apr 12, 2024
ae8441f
fix checking for multiple input flags in encrypt
jakedoublev Apr 12, 2024
ddb4868
put back TOKEN_URL until it's pulled from well-known config
jakedoublev Apr 12, 2024
6248798
keep consistent casing throughout the CLI flags by renaming camel cas…
jakedoublev Apr 15, 2024
10a5440
cleanup
jakedoublev Apr 15, 2024
0ece058
Merge branch 'main' into feat/tdf-demo
jakedoublev Apr 15, 2024
029688f
fixup config file reference
jakedoublev Apr 15, 2024
7da7d46
Merge branch 'main' into feat/tdf-demo
jakedoublev Apr 22, 2024
7bd3269
support piping in encrypt/decrypt and update flags/docs accordingly w…
jakedoublev Apr 22, 2024
f941308
cleanup
jakedoublev Apr 22, 2024
5453912
make sure only policy commands mention --json flag
jakedoublev Apr 22, 2024
5f74225
fix(core): Support passing '-o' to decrypt (#128)
b-long Apr 24, 2024
6cb7de7
cleanup
jakedoublev Apr 24, 2024
77da0f2
pr feedback
jakedoublev Apr 24, 2024
95ba74e
cleanup and decrypt stdin functionality
jakedoublev Apr 24, 2024
d8c9cb5
improve piping support
jakedoublev Apr 24, 2024
daa5e93
improve docs
jakedoublev Apr 24, 2024
a88c78b
merge branch 'main' into feat/tdf-demo
jakedoublev Apr 24, 2024
370352c
Merge branch 'main' into feat/tdf-demo
jakedoublev Apr 24, 2024
b9cdaff
cleanup
jakedoublev Apr 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ otdfctl
public/
.hugo_build.lock
output/

# Ignore any TDF files created by the CLI
*.tdf
24 changes: 12 additions & 12 deletions cmd/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ func auth_clientCredentials(cmd *cobra.Command, args []string) {
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
clientId := flagHelper.GetOptionalString("clientId")
clientSecret := flagHelper.GetOptionalString("clientSecret")
clientId := flagHelper.GetOptionalString("client-id")
clientSecret := flagHelper.GetOptionalString("client-secret")
// noCache := flagHelper.GetOptionalString("noCache")
errMsg := fmt.Sprintf("Please provide required flag: (%s)", "Param Not Found")

// h.DEBUG_PrintKeyRingSecrets()

// check if we have a clientId in the keyring, if a null value is passed in
if clientId == "" {
fmt.Println("No clientId provided. Attempting to retrieve the default from keyring.")
fmt.Println("No client-id provided. Attempting to retrieve the default from keyring.")
retrievedClientID, errID := keyring.Get(handlers.TOKEN_URL, handlers.OTDFCTL_CLIENT_ID_CACHE_KEY)
if errID == nil {
clientId = retrievedClientID
Expand All @@ -34,7 +34,7 @@ func auth_clientCredentials(cmd *cobra.Command, args []string) {

// now lets check if we still don't have it, and if not, throw and error
if clientId == "" {
errMsg = fmt.Sprintf("Please provide required flag: (%s)", "clientId")
errMsg = fmt.Sprintf("Please provide required flag: (%s)", "client-id")
fmt.Println(cli.ErrorMessage(errMsg, nil))
cli.ExitWithError("Failed to create attribute", nil)
return
Expand All @@ -50,7 +50,7 @@ func auth_clientCredentials(cmd *cobra.Command, args []string) {
}
// check if we still don't have it, and if not throw an error
if clientSecret == "" {
errMsg = fmt.Sprintf("Please provide required flag: (%s)", "clientSecret")
errMsg = fmt.Sprintf("Please provide required flag: (%s)", "client-secret")
fmt.Println(cli.ErrorMessage(errMsg, nil))
cli.ExitWithError("Failed to create attribute", nil)
return
Expand All @@ -65,22 +65,22 @@ func auth_clientCredentials(cmd *cobra.Command, args []string) {
return
}

fmt.Println(cli.SuccessMessage("Successfully logged in with clientId and clientSecret"))
fmt.Println(cli.SuccessMessage("Successfully logged in with client ID and secret"))
}

func init() {
clientCredentialsCmd := man.Docs.GetCommand("auth/client-credentials",
man.WithRun(auth_clientCredentials),
)
clientCredentialsCmd.Flags().String(
clientCredentialsCmd.GetDocFlag("clientId").Name,
clientCredentialsCmd.GetDocFlag("clientId").Default,
clientCredentialsCmd.GetDocFlag("clientId").Description,
clientCredentialsCmd.GetDocFlag("client-id").Name,
clientCredentialsCmd.GetDocFlag("client-id").Default,
clientCredentialsCmd.GetDocFlag("client-id").Description,
)
clientCredentialsCmd.Flags().String(
clientCredentialsCmd.GetDocFlag("clientSecret").Name,
clientCredentialsCmd.GetDocFlag("clientSecret").Default,
clientCredentialsCmd.GetDocFlag("clientSecret").Description,
clientCredentialsCmd.GetDocFlag("client-secret").Name,
clientCredentialsCmd.GetDocFlag("client-secret").Default,
clientCredentialsCmd.GetDocFlag("client-secret").Description,
)

cmd := man.Docs.GetCommand("auth",
Expand Down
39 changes: 39 additions & 0 deletions cmd/dev.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package cmd

import (
"bufio"
"encoding/json"
"fmt"
"io"
"os"
"strings"

"github.com/charmbracelet/lipgloss/table"
Expand Down Expand Up @@ -114,6 +117,42 @@ func injectLabelFlags(cmd *cobra.Command, isUpdate bool) {
}
}

// Read bytes from stdin without blocking by checking size first
func readPipedStdin() []byte {
stat, err := os.Stdin.Stat()
if err != nil {
cli.ExitWithError("Failed to read stat from stdin", err)
}
if (stat.Mode() & os.ModeCharDevice) == 0 {
var buf []byte
scanner := bufio.NewScanner(os.Stdin)

for scanner.Scan() {
buf = append(buf, scanner.Bytes()...)
}

if err := scanner.Err(); err != nil {
cli.ExitWithError("failed to scan bytes from stdin", err)
}
return buf
}
return nil
}

func readBytesFromFile(filePath string) []byte {
fileToEncrypt, err := os.Open(filePath)
if err != nil {
cli.ExitWithError(fmt.Sprintf("Failed to open file at path: %s", filePath), err)
}
defer fileToEncrypt.Close()

bytes, err := io.ReadAll(fileToEncrypt)
if err != nil {
cli.ExitWithError(fmt.Sprintf("Failed to read bytes from file at path: %s", filePath), err)
}
return bytes
}

func init() {
designCmd := man.Docs.GetCommand("dev/design-system",
man.WithRun(dev_designSystem),
Expand Down
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/opentdf/otdfctl/internal/config"
"github.com/opentdf/otdfctl/pkg/man"
"github.com/spf13/cobra"
)

var (
Expand Down Expand Up @@ -36,6 +37,7 @@ func init() {
doc.GetDocFlag("log-level").Default,
doc.GetDocFlag("log-level").Description,
)
RootCmd.AddGroup(&cobra.Group{ID: "tdf"})
}

// Execute adds all child commands to the root command and sets flags appropriately.
Expand Down
70 changes: 70 additions & 0 deletions cmd/tdf-decrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package cmd

import (
"errors"
"fmt"
"os"

"github.com/opentdf/otdfctl/pkg/cli"
"github.com/opentdf/otdfctl/pkg/man"
"github.com/spf13/cobra"
)

func dev_tdfDecryptCmd(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
output := flagHelper.GetOptionalString("out")

// check for piped input
piped := readPipedStdin()

// Prefer file argument over piped input over default filename
var bytesToDecrypt []byte
var tdfFile string
if len(args) > 0 {
tdfFile = args[0]
bytesToDecrypt = readBytesFromFile(tdfFile)
} else if len(piped) > 0 {
bytesToDecrypt = piped
} else {
cli.ExitWithError("Must provide ONE of the following to decrypt: [file argument, stdin input]", errors.New("no input provided"))
}

decrypted, err := h.DecryptTDF(bytesToDecrypt)
if err != nil {
cli.ExitWithError("Failed to decrypt file", err)
}

if output == "" {
// Print decrypted content to stdout
fmt.Print(decrypted.String())
return
}
// Here 'output' is the filename given with -o
f, err := os.Create(output)
if err != nil {
cli.ExitWithError("Failed to write decrypted data to file", err)
}
defer f.Close()
_, err = f.Write(decrypted.Bytes())
if err != nil {
cli.ExitWithError("Failed to write decrypted data to file", err)
}
}

func init() {
decryptCmd := man.Docs.GetCommand("decrypt",
man.WithRun(dev_tdfDecryptCmd),
)
decryptCmd.Flags().StringP(
decryptCmd.GetDocFlag("out").Name,
decryptCmd.GetDocFlag("out").Shorthand,
decryptCmd.GetDocFlag("out").Default,
decryptCmd.GetDocFlag("out").Description,
)
decryptCmd.Command.GroupID = "tdf"

RootCmd.AddCommand(&decryptCmd.Command)
}
99 changes: 99 additions & 0 deletions cmd/tdf-encrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package cmd

import (
"fmt"
"io"
"os"
"strings"

"github.com/opentdf/otdfctl/pkg/cli"
"github.com/opentdf/otdfctl/pkg/man"
"github.com/spf13/cobra"
)

func dev_tdfEncryptCmd(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
var filePath string
if len(args) > 0 {
filePath = args[0]
}
out := flagHelper.GetOptionalString("out")
values := flagHelper.GetStringSlice("attr", attrValues, cli.FlagHelperStringSliceOptions{Min: 0})

piped := readPipedStdin()

inputCount := 0
if filePath != "" {
inputCount++
}
if len(piped) > 0 {
inputCount++
}

if inputCount == 0 {
cli.ExitWithError("Must provide ONE of the following to encrypt: [file argument, stdin input]", nil)
} else if inputCount > 1 {
cli.ExitWithError("Must provide ONLY ONE of the following to encrypt: [file argument, stdin input]", nil)
}

// prefer filepath argument over stdin input
var bytes []byte
if filePath != "" {
bytes = readBytesFromFile(filePath)
} else {
bytes = piped
}

// Do the encryption
encrypted, err := h.EncryptBytes(bytes, values)
if err != nil {
cli.ExitWithError("Failed to encrypt", err)
}

// Find the destination as the output flag filename or stdout
var dest *os.File
if out != "" {
// make sure output ends in .tdf extension
if !strings.HasSuffix(out, ".tdf") {
out += ".tdf"
}
tdfFile, err := os.Create(out)
if err != nil {
cli.ExitWithError(fmt.Sprintf("Failed to write encrypted file %s", out), err)
}
defer tdfFile.Close()
dest = tdfFile
} else {
dest = os.Stdout
}

_, e := io.Copy(dest, encrypted)
if e != nil {
cli.ExitWithError("Failed to write encrypted data to stdout", e)
}
}

func init() {
encryptCmd := man.Docs.GetCommand("encrypt",
man.WithRun(dev_tdfEncryptCmd),
)
encryptCmd.Flags().StringP(
encryptCmd.GetDocFlag("out").Name,
encryptCmd.GetDocFlag("out").Shorthand,
encryptCmd.GetDocFlag("out").Default,
encryptCmd.GetDocFlag("out").Description,
)
encryptCmd.Flags().StringSliceVarP(
&attrValues,
encryptCmd.GetDocFlag("attr").Name,
encryptCmd.GetDocFlag("attr").Shorthand,
[]string{},
encryptCmd.GetDocFlag("attr").Description,
)
encryptCmd.Command.GroupID = "tdf"

RootCmd.AddCommand(&encryptCmd.Command)
}
8 changes: 4 additions & 4 deletions docs/man/auth/client-credentials.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
---
title: Set up client credentials
title: Authenticate to the platform with the client-credentials flow

command:
name: client-credentials
flags:
- name: clientId
- name: client-id
description: Client ID
required: true
- name: clientSecret
- name: client-secret
description: Client secret
---

Allows the user to login in via clientId and clientSecret. This will subsequently be stored in the
Allows the user to login in via client ID and secret. This will subsequently be stored in the
OS-specific keychain by default.
26 changes: 26 additions & 0 deletions docs/man/decrypt/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
title: Decrypt a TDF file
command:
name: decrypt [file]
flags:
- name: out
shorthand: o
description: 'The file destination for decrypted content to be written instead of stdout.'
default: ''
---

Decrypt a Trusted Data Format (TDF) file and output the contents to stdout or a file in the current working directory.

The first argument is the TDF file with path from the current working directory being decrypted.

## Examples:

```bash
# specify the TDF to decrypt then output decrypted contents
otdfctl decrypt hello.txt.tdf # write to stdout
otdfctl decrypt hello.txt.tdf > hello.txt # consume stdout to write to hello.txt file
otdfctl decrypt hello.txt.tdf -o hello.txt # write to hello.txt file instead of stdout

# pipe the TDF to decrypt
cat hello.txt.tdf | otdfctl decrypt > hello.txt
```
30 changes: 30 additions & 0 deletions docs/man/encrypt/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
title: Encrypt file or stdin as a TDF
command:
name: encrypt [file]
flags:
- name: out
shorthand: o
description: The output file TDF in the current working directory instead of stdout ('-o file.txt' and '-o file.txt.tdf' both write the TDF as file.txt.tdf).
default: ''
- name: attr
shorthand: a
description: Attribute value Fully Qualified Names (FQNs, i.e. 'https://example.com/attr/attr1/value/value1') to apply to the encrypted data.
---

Build a Trusted Data Format (TDF) with encrypted content from a specified file or input from stdin utilizing OpenTDF platform.

## Examples:

```bash
# output to stdout
echo "some text" | otdfctl encrypt
otdfctl encrypt hello.txt
# pipe stdout to a bucket
echo "my secret" | otdfctl encrypt | aws s3 cp - s3://my-bucket/secret.txt.tdf

# output hello.txt.tdf in root directory
echo "hello world" | otdfctl encrypt -o hello.txt
cat hello.txt | otdfctl encrypt -o hello.txt
cat hello.txt | otdfctl encrypt -o hello.txt.tdf #.tdf extension is only added once
```
Loading