Skip to content

Commit

Permalink
v0.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
dkyanakiev authored and Dimitar Yanakiev committed Nov 27, 2023
1 parent ea0a6d9 commit 1093c58
Show file tree
Hide file tree
Showing 62 changed files with 5,042 additions and 2 deletions.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,11 @@

# Go workspace file
go.work
.idea

.DS_Store
.vscode/*
vendor/*
bin
bin/*
pkg/
26 changes: 26 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
local-vault:
vault server -dev

setup-test-data:
./helpers/setup.sh

.PHONY: install-osx
install-osx:
cp ./bin/vaul7y /usr/local/bin/vaul7y

.PHONY: dev
dev: ## Build for the current development version
@echo "==> Building Vaul7y..."
@mkdir -p ./bin
@CGO_ENABLED=0 go build -o ./bin/vaul7y ./cmd/vaul7y
@rm -f $(GOPATH)/bin/vaul7y
@cp ./bin/vaul7y/vaul7y $(GOPATH)/bin/vaul7y
@echo "==> Done"

.PHONY: build
build:
go build -o bin/vaul7y ./cmd/vaul7y

.PHONY: run
run:
./bin/vaul7y
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
# vaulty
# Vaul7y / Vaulty

Simple Vault CLI
Vaulty is a TUI for Hashicorp Vault. The goal is to support as many functionalities as possible in order to make the tool as usefu as possible.

## Why use Vaul7y

I started the tool purely for personal use as I love tools like [K9s](https://github.com/derailed/k9s) or [Wander](https://github.com/robinovitch61/wander). I generally prefer the use of CLI tools but when it came to vault and looking up at stuff, sometimes having a UI just speeds things up. I couldn't find something finished, so decided to write my own.

## Video
![gif](./images/vaulty-min.gif)

## Usage

To see detailed guide on how to use the tool see the [docs](./docs/usage.md)

## Features and Bugs

The tool is in active development and is bug heavy. There are multiple things that are on my short and long term TODO list.

If anyone decides to use this and wants to request a specific feature or even fix a bug - please open an issue :smile:

## Short term TODO list:
1. Change the logger (current one is a mess)
1. Finish adding tests (wip in another branch)
2. Finish implementing Update to existing secrets
* Bonus: Create net new ones.
3. Support for namespace changes.
1 change: 1 addition & 0 deletions cmd/vaul7y/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package main
138 changes: 138 additions & 0 deletions cmd/vaul7y/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package main

import (
"log"
"os"
"time"

"github.com/dkyanakiev/vaulty/component"
"github.com/dkyanakiev/vaulty/state"
"github.com/dkyanakiev/vaulty/vault"
"github.com/dkyanakiev/vaulty/view"
"github.com/dkyanakiev/vaulty/watcher"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)

var refreshIntervalDefault = time.Second * 5
var logger *log.Logger

func main() {

//

LOG_FILE, exists := os.LookupEnv("VAULTY_LOG_FILE")
if !exists {
LOG_FILE = "/tmp/vaulty-errors"
} // open log file
logFile, err := os.OpenFile(LOG_FILE, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
log.Panic(err)
}
defer logFile.Close()

logger = log.New(logFile, "Vaulty ", log.LstdFlags)
logger.SetOutput(logFile)

// optional: log date-time, filename, and line number
logger.SetFlags(log.Lshortfile | log.LstdFlags)

tview.Styles.PrimitiveBackgroundColor = tcell.NewRGBColor(40, 44, 48)

vaultClient, err := vault.New(func(v *vault.Vault) error {
return vault.Default(v, logger)
})
state := initializeState(vaultClient)
commands := component.NewCommands()
vaultInfo := component.NewVaultInfo()
mounts := component.NewMountsTable()
policies := component.NewPolicyTable()
policyAcl := component.NewPolicyAclTable()
secrets := component.NewSecretsTable()
secretObj := component.NewSecretObjTable()
logo := component.NewLogo()
info := component.NewInfo()
failure := component.NewInfo()
errorComp := component.NewError()
components := &view.Components{
VaultInfo: vaultInfo,
Commands: commands,
MountsTable: mounts,
PolicyTable: policies,
PolicyAclTable: policyAcl,
SecretsTable: secrets,
SecretObjTable: secretObj,
Info: info,
Error: errorComp,
Failure: failure,
Logo: logo,
Logger: logger,
}
watcher := watcher.NewWatcher(state, vaultClient, refreshIntervalDefault, logger)
view := view.New(components, watcher, vaultClient, state, logger)

view.Init("0.0.1")
err = view.Layout.Container.Run()
if err != nil {
log.Fatal("cannot initialize view.")
}

}

func initializeState(client *vault.Vault) *state.State {
state := state.New()
addr := client.Address()
state.VaultAddress = addr
state.Namespace = "default"

return state
}

func initLogger() {

// TODO Rework later
LOG_FILE := "/tmp/vaulty-errors"
// open log file
logFile, err := os.OpenFile(LOG_FILE, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
log.Panic(err)
}
defer logFile.Close()

logger = log.New(logFile, "app ", log.LstdFlags)

// Set log out put and enjoy :)
logger.SetOutput(logFile)

// optional: log date-time, filename, and line number
logger.SetFlags(log.Lshortfile | log.LstdFlags)

}

// // LOOK AT LATER
// func main() {
// vaultClient, _ := vault.New(vault.Default)
// //ctx := context.TODO()
// // mounts, _ := vaultClient.Sys.ListMounts()

// secret, _ := vaultClient.ListSecrets("kv0FF76557")
// log.Println(secret)

// secrets, _ := vaultClient.ListNestedSecrets("kv0FF76557", "")
// //secrets, err := vaultClient.Logical.List("randomkv/metadata/test/one")

// for _, value := range secrets {
// fmt.Printf("Key: %s\n", value.PathName)
// fmt.Printf("IsSecret: %t\n", value.IsSecret)
// }
// // val, err := vaultClient.KV2.Get(ctx, "path")
// // fmt.Println(val)
// // fmt.Println(err)

// // secretClient, err := vaultClient.Logical.List("credentials/metadata/")
// // if err != nil {
// // // TODO
// // fmt.Println(err)
// // }
// // vault.DataIterator(secretClient.Data["keys"])
// }
96 changes: 96 additions & 0 deletions component/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package component

import (
"fmt"
"strings"

"github.com/rivo/tview"

primitive "github.com/dkyanakiev/vaulty/primitives"
"github.com/dkyanakiev/vaulty/styles"
)

var (
MainCommands = []string{
fmt.Sprintf("%sMain Commands:", styles.HighlightSecondaryTag),
fmt.Sprintf("%s<ctrl-m>%s to display System Mounts", styles.HighlightPrimaryTag, styles.StandardColorTag),
fmt.Sprintf("%s<ctrl-p>%s to display ACL Policies", styles.HighlightPrimaryTag, styles.StandardColorTag),
fmt.Sprintf("%s<ctrl-c>%s to Quit", styles.HighlightPrimaryTag, styles.StandardColorTag),
}
MountsCommands = []string{
fmt.Sprintf("\n%s Secret Mounts Command List:", styles.HighlightSecondaryTag),
fmt.Sprintf("%s<e>%s to explore mount", styles.HighlightPrimaryTag, styles.StandardColorTag),
}
NoViewCommands = []string{}
PolicyCommands = []string{
fmt.Sprintf("\n%s ACL Policy Commands:", styles.HighlightSecondaryTag),
fmt.Sprintf("%s<i>%s to inspect policy", styles.HighlightPrimaryTag, styles.StandardColorTag),
fmt.Sprintf("%s</>%s apply filter", styles.HighlightPrimaryTag, styles.StandardColorTag),
}
PolicyACLCommands = []string{
fmt.Sprintf("\n%s ACL Policy Commands:", styles.HighlightSecondaryTag),
fmt.Sprintf("%s<Esc>%s to go back", styles.HighlightPrimaryTag, styles.StandardColorTag),
//fmt.Sprintf("%s</>%s apply filter", styles.HighlightPrimaryTag, styles.StandardColorTag),
}
SecretsCommands = []string{
fmt.Sprintf("\n%s Secrets Commands:", styles.HighlightSecondaryTag),
fmt.Sprintf("%s<e>%s to navigate to selected the path", styles.HighlightPrimaryTag, styles.StandardColorTag),
fmt.Sprintf("%s<b>%s to go back to the previous path", styles.HighlightPrimaryTag, styles.StandardColorTag),
}
SecretObjectCommands = []string{
fmt.Sprintf("\n%s Secret Commands:", styles.HighlightSecondaryTag),
fmt.Sprintf("%s<h>%s toggle display for secrets", styles.HighlightPrimaryTag, styles.StandardColorTag),
fmt.Sprintf("%s<c>%s copy secret to clipboard", styles.HighlightPrimaryTag, styles.StandardColorTag),
fmt.Sprintf("%s<j>%s toggle json view for secret", styles.HighlightPrimaryTag, styles.StandardColorTag),
//TODO: Work in progress
//fmt.Sprintf("%s<p>%s patch secret", styles.HighlightPrimaryTag, styles.StandardColorTag),
}
)

type Commands struct {
TextView TextView
Props *CommandsProps
slot *tview.Flex
}

type CommandsProps struct {
MainCommands []string
ViewCommands []string
}

func NewCommands() *Commands {
return &Commands{
TextView: primitive.NewTextView(tview.AlignLeft),
Props: &CommandsProps{
MainCommands: MainCommands,
// ViewCommands: MainCommands,
},
}
}

func (c *Commands) Update(commands []string) {
c.Props.ViewCommands = commands

c.updateText()
}

func (c *Commands) Render() error {
if c.slot == nil {
return ErrComponentNotBound
}

c.updateText()

c.slot.AddItem(c.TextView.Primitive(), 0, 1, false)
return nil
}

func (c *Commands) updateText() {
commands := append(c.Props.MainCommands, c.Props.ViewCommands...)
cmds := strings.Join(commands, "\n")
c.TextView.SetText(cmds)
}

func (c *Commands) Bind(slot *tview.Flex) {
c.slot = slot
}
84 changes: 84 additions & 0 deletions component/components.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package component

import (
"github.com/dkyanakiev/vaulty/models"
"github.com/dkyanakiev/vaulty/primitives"

"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)

const (
ErrComponentNotBound = models.Comp("component not bound")
ErrComponentPropsNotSet = models.Comp("component properties not set")
)

//go:generate counterfeiter . DoneModalFunc
type DoneModalFunc func(buttonIndex int, buttonLabel string)

type Primitive interface {
Primitive() tview.Primitive
}

//go:generate counterfeiter . Table
type Table interface {
Primitive
SetTitle(format string, args ...interface{})
GetCellContent(row, column int) string
GetSelection() (row, column int)
Clear()
RenderHeader(data []string)
RenderRow(data []string, index int, c tcell.Color)
SetSelectedFunc(fn func(row, column int))
SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey)
}

//go:generate counterfeiter . TextView
type TextView interface {
Primitive
GetText(bool) string
SetText(text string) *tview.TextView
Write(data []byte) (int, error)
Highlight(regionIDs ...string) *tview.TextView
Clear() *tview.TextView
ModifyPrimitive(f func(t *tview.TextView))
}

//go:generate counterfeiter . Modal
type Modal interface {
Primitive
SetDoneFunc(handler func(buttonIndex int, buttonLabel string))
SetText(text string)
SetFocus(index int)
Container() tview.Primitive
}

type Form interface {
Primitive
Container() tview.Primitive
}

//go:generate counterfeiter . InputField
type InputField interface {
Primitive
SetDoneFunc(handler func(k tcell.Key))
SetChangedFunc(handler func(text string))
SetAutocompleteFunc(callback func(currentText string) (entries []string))
SetText(text string)
GetText() string
}

//go:generate counterfeiter . DropDown
type DropDown interface {
Primitive
SetOptions(options []string, selected func(text string, index int))
SetCurrentOption(index int)
SetSelectedFunc(selected func(text string, index int))
}

//go:generate counterfeiter . Selector
type Selector interface {
Primitive
GetTable() *primitives.Table
Container() tview.Primitive
}
Loading

0 comments on commit 1093c58

Please sign in to comment.