Skip to content
This repository has been archived by the owner on Feb 16, 2023. It is now read-only.

Commit

Permalink
Merge pull request #345 from secrethub/release/v0.41.0
Browse files Browse the repository at this point in the history
Release v0.41.0
  • Loading branch information
florisvdg authored Sep 8, 2020
2 parents 4a10069 + ca1b6db commit d5e091a
Show file tree
Hide file tree
Showing 19 changed files with 966 additions and 142 deletions.
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<br/>

<p align="center">
<a href="https://secrethub.io/docs/start/getting-started/"><img alt="Get Started" src="https://secrethub.io/img/buttons/github/get-started.png?v1" height="28" /></a>
<a href="https://signup.secrethub.io/"><img alt="Get Started" src="https://secrethub.io/img/buttons/github/get-started.png?v1" height="28" /></a>
<a href="https://secrethub.io/docs/reference/cli/"><img alt="View Docs" src="https://secrethub.io/img/buttons/github/view-docs.png?v2" height="28" /></a>
</p>
<br/>
Expand All @@ -21,10 +21,6 @@ The SecretHub CLI provides the command-line interface to interact with the Secre

> [SecretHub][secrethub] is a secrets management tool that works for every engineer. Securely provision passwords and keys throughout your entire stack with just a few lines of code.
## Get started

Follow the [Getting Started Guide][getting-started] to quickly get up and running with SecretHub :rocket:

## Usage

Below you can find a selection of some of the most-used SecretHub commands. Run `secrethub --help` or the [CLI reference docs][cli-reference-docs] for a complete list of all commands.
Expand Down
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,12 @@ require (
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pkg/errors v0.9.1 // indirect
github.com/secrethub/demo-app v0.1.0
github.com/secrethub/secrethub-go v0.30.0
github.com/secrethub/secrethub-go v0.31.0
github.com/zalando/go-keyring v0.0.0-20190208082241-fbe81aec3a07
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e
golang.org/x/text v0.3.2
google.golang.org/api v0.26.0
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84
gopkg.in/yaml.v2 v2.2.2
gotest.tools v2.2.0+incompatible
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ github.com/secrethub/secrethub-go v0.29.1-0.20200707154958-5e5602145597 h1:uC9OD
github.com/secrethub/secrethub-go v0.29.1-0.20200707154958-5e5602145597/go.mod h1:tDeBtyjfFQX3UqgaZfY+H4dYkcGfiVzrwLDf0XtfOrw=
github.com/secrethub/secrethub-go v0.30.0 h1:Nh1twPDwPbYQj/cYc1NG+j7sv76LZiXLPovyV83tZj0=
github.com/secrethub/secrethub-go v0.30.0/go.mod h1:tDeBtyjfFQX3UqgaZfY+H4dYkcGfiVzrwLDf0XtfOrw=
github.com/secrethub/secrethub-go v0.31.0 h1:0KoG0KHBOa5knkvf3K0f6sKuPSQ5VGPXLD4ttC9Eul8=
github.com/secrethub/secrethub-go v0.31.0/go.mod h1:ZIco8Y0G0Pi0Vb7pQROjvEKgSreZiRMLhAbzWUneUSQ=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
Expand Down
3 changes: 2 additions & 1 deletion internals/cli/filemode/filemode.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package filemode

import (
"fmt"
"os"
"strconv"

Expand Down Expand Up @@ -47,7 +48,7 @@ func (m *FileMode) Set(value string) error {

// String implements the flag.Value interface.
func (m FileMode) String() string {
return string(m)
return fmt.Sprintf("%#o", m.FileMode().Perm())
}

// FileMode returns the file mode as an os.FileMode.
Expand Down
25 changes: 14 additions & 11 deletions internals/cli/masker/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package masker
import (
"bytes"
"io"
"io/ioutil"
"sync"
"time"
)
Expand Down Expand Up @@ -64,31 +65,32 @@ func (s *stream) flush(n int) error {

if exists {
// Get any unprocessed bytes before this match to the destination.
beforeMatch := s.buf.upToIndex(i)

_, err := s.dest.Write(beforeMatch)
bytesBeforeMatch, err := s.buf.writeUpToIndex(s.dest, i)
if err != nil {
return err
}

// Only write the redaction text if there were bytes between this match and the previous match
// or this is the first flush for the buffer.
if len(beforeMatch) > 0 || s.buf.currentIndex == 0 {
if bytesBeforeMatch > 0 || s.buf.currentIndex == 0 {
_, err = s.dest.Write([]byte("<redacted by SecretHub>"))
if err != nil {
return err
}
}

// Drop all bytes until the end of the mask.
_ = s.buf.upToIndex(i + int64(length))
_, err = s.buf.writeUpToIndex(ioutil.Discard, i+int64(length))
if err != nil {
return err
}

delete(s.matches, i)
}
}

// Write all bytes after the last match.
_, err := s.dest.Write(s.buf.upToIndex(endIndex))
_, err := s.buf.writeUpToIndex(s.dest, endIndex)
if err != nil {
return err
}
Expand All @@ -109,16 +111,17 @@ func (b *indexedBuffer) write(p []byte) (n int, err error) {
return b.buffer.Write(p)
}

// upToIndex pops and returns all bytes in the buffer up to the given index.
// If all bytes up to this given index have already been returned previously, an empty slice is returned.
func (b *indexedBuffer) upToIndex(index int64) []byte {
// writeUpToIndex pops all bytes in the buffer up to the given index and writes them to the given writer.
// The number of bytes written and any errors encountered are returned
func (b *indexedBuffer) writeUpToIndex(w io.Writer, index int64) (int, error) {
b.mutex.Lock()
defer b.mutex.Unlock()

if index < b.currentIndex {
return []byte{}
return 0, nil
}
n := int(index - b.currentIndex)
b.currentIndex = index
return b.buffer.Next(n)
bufferSlice := b.buffer.Next(n)
return w.Write(bufferSlice)
}
42 changes: 40 additions & 2 deletions internals/demo/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,15 @@ func (cmd *InitCommand) Run() error {
}

_, err = client.Repos().Create(repoPath)
if err == api.ErrRepoAlreadyExists && cmd.repo == "" {
return fmt.Errorf("demo repo %s already exists, use --repo to specify another repo to use", repoPath)
if err == api.ErrRepoAlreadyExists {
demoRepo, err := cmd.isDemoRepo(client, repoPath)
if err != nil {
return err
}
if demoRepo {
return nil
}
return fmt.Errorf("repo %s already exists and is not a demo repo, use --repo to specify another repo to use", repoPath)
} else if err != nil {
return err
}
Expand All @@ -89,3 +96,34 @@ func (cmd *InitCommand) Run() error {

return nil
}

// isDemoRepo checks whether the repo on the given path is a demo repository.
// It returns true iff the repository contains exactly two secrets named username and password.
func (cmd *InitCommand) isDemoRepo(client secrethub.ClientInterface, repoPath string) (bool, error) {
repo, err := client.Repos().Get(repoPath)
if err != nil {
return false, err
}
if repo.SecretCount != 2 {
return false, nil
}

usernamePath := secretpath.Join(repoPath, "username")
exists, err := client.Secrets().Exists(usernamePath)
if err != nil {
return false, err
}
if !exists {
return false, nil
}

passwordPath := secretpath.Join(repoPath, "password")
exists, err = client.Secrets().Exists(passwordPath)
if err != nil {
return false, err
}
if !exists {
return false, nil
}
return true, nil
}
2 changes: 2 additions & 0 deletions internals/secrethub/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ func NewApp() *App {
io := ui.NewUserIO()
store := NewCredentialConfig(io)
help := "The SecretHub command-line interface is a unified tool to manage your infrastructure secrets with SecretHub.\n\n" +
"If you do not yet have a SecretHub account, go here to create one:\n\n" +
" https://signup.secrethub.io/\n\n" +
"For a step-by-step introduction, check out:\n\n" +
" https://secrethub.io/docs/getting-started/\n\n" +
"To get help, see:\n\n" +
Expand Down
2 changes: 1 addition & 1 deletion internals/secrethub/credential_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

// Errors
var (
ErrCredentialNotExist = errMain.Code("credential_not_exist").Error("could not find credential file. Run `secrethub signup` to create an account.")
ErrCredentialNotExist = errMain.Code("credential_not_exist").Error("could not find credential file. Go to https://signup.secrethub.io/ to create an account or run `secrethub init` to use an already existing account on this machine.")
)

// CredentialConfig handles the configuration necessary for local credentials.
Expand Down
2 changes: 1 addition & 1 deletion internals/secrethub/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ func (cmd *EnvCommand) Register(r command.Registerer) {
clause := r.Command("env", "[BETA] Manage environment variables.").Hidden()
clause.HelpLong("This command is hidden because it is still in beta. Future versions may break.")
NewEnvReadCommand(cmd.io, cmd.newClient).Register(clause)
NewEnvListCommand(cmd.io).Register(clause)
NewEnvListCommand(cmd.io, cmd.newClient).Register(clause)
}
4 changes: 2 additions & 2 deletions internals/secrethub/env_ls.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ type EnvListCommand struct {
}

// NewEnvListCommand creates a new EnvListCommand.
func NewEnvListCommand(io ui.IO) *EnvListCommand {
func NewEnvListCommand(io ui.IO, newClient newClientFunc) *EnvListCommand {
return &EnvListCommand{
io: io,
environment: newEnvironment(io),
environment: newEnvironment(io, newClient),
}
}

Expand Down
2 changes: 1 addition & 1 deletion internals/secrethub/env_read.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func NewEnvReadCommand(io ui.IO, newClient newClientFunc) *EnvReadCommand {
return &EnvReadCommand{
io: io,
newClient: newClient,
environment: newEnvironment(io),
environment: newEnvironment(io, newClient),
}
}

Expand Down
86 changes: 85 additions & 1 deletion internals/secrethub/env_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
Expand All @@ -22,8 +23,19 @@ import (
"gopkg.in/yaml.v2"
)

type errNameCollision struct {
name string
firstPath string
secondPath string
}

func (e errNameCollision) Error() string {
return fmt.Sprintf("secrets at path %s and %s map to the same environment variable: %s. Rename one of the secrets or source them in a different way", e.firstPath, e.secondPath, e.name)
}

type environment struct {
io ui.IO
newClient newClientFunc
osEnv []string
readFile func(filename string) ([]byte, error)
osStat func(filename string) (os.FileInfo, error)
Expand All @@ -32,12 +44,14 @@ type environment struct {
templateVars map[string]string
templateVersion string
dontPromptMissingTemplateVar bool
secretsDir string
secretsEnvDir string
}

func newEnvironment(io ui.IO) *environment {
func newEnvironment(io ui.IO, newClient newClientFunc) *environment {
return &environment{
io: io,
newClient: newClient,
osEnv: os.Environ(),
readFile: ioutil.ReadFile,
osStat: os.Stat,
Expand All @@ -53,6 +67,7 @@ func (env *environment) register(clause *cli.CommandClause) {
clause.Flag("var", "Define the value for a template variable with `VAR=VALUE`, e.g. --var env=prod").Short('v').StringMapVar(&env.templateVars)
clause.Flag("template-version", "The template syntax version to be used. The options are v1, v2, latest or auto to automatically detect the version.").Default("auto").StringVar(&env.templateVersion)
clause.Flag("no-prompt", "Do not prompt when a template variable is missing and return an error instead.").BoolVar(&env.dontPromptMissingTemplateVar)
clause.Flag("secrets-dir", "Recursively include all secrets from a directory. Environment variable names are derived from the path of the secret: `/` are replaced with `_` and the name is uppercased.").StringVar(&env.secretsDir)
clause.Flag("env", "The name of the environment prepared by the set command (default is `default`)").Default("default").Hidden().StringVar(&env.secretsEnvDir)
}

Expand All @@ -75,6 +90,12 @@ func (env *environment) env() (map[string]value, error) {
sources = append(sources, dirSource)
}

// --secrets-dir flag
if env.secretsDir != "" {
secretsDirEnv := newSecretsDirEnv(env.newClient, env.secretsDir)
sources = append(sources, secretsDirEnv)
}

//secrethub.env file
if env.envFile == "" {
_, err := env.osStat(defaultEnvFile)
Expand Down Expand Up @@ -173,6 +194,69 @@ func newSecretValue(path string) value {
return &secretValue{path: path}
}

// secretsDirEnv sources environment variables from the directory specified with the --secrets-dir flag.
type secretsDirEnv struct {
newClient newClientFunc
dirPath string
}

// env returns a map of environment variables containing all secrets from the specified path.
// The variable names are the relative paths of their corresponding secrets in uppercase snake case.
// An error is returned if two secret paths map to the same variable name.
func (s *secretsDirEnv) env() (map[string]value, error) {
client, err := s.newClient()
if err != nil {
return nil, err
}

tree, err := client.Dirs().GetTree(s.dirPath, -1, false)
if err != nil {
return nil, err
}

paths := make(map[string]string, tree.SecretCount())
for id := range tree.Secrets {
secretPath, err := tree.AbsSecretPath(id)
if err != nil {
return nil, err
}
path := secretPath.String()

envVarName := s.envVarName(path)
if prevPath, found := paths[envVarName]; found {
return nil, errNameCollision{
name: envVarName,
firstPath: prevPath,
secondPath: path,
}
}
paths[envVarName] = path
}

result := make(map[string]value, len(paths))
for name, path := range paths {
result[name] = newSecretValue(path)
}
return result, nil
}

// envVarName returns the environment variable name corresponding to the secret on the specified path
// by converting the relative path to uppercase snake case.
func (s *secretsDirEnv) envVarName(path string) string {
envVarName := strings.TrimPrefix(path, s.dirPath)
envVarName = strings.TrimPrefix(envVarName, "/")
envVarName = strings.ReplaceAll(envVarName, "/", "_")
envVarName = strings.ToUpper(envVarName)
return envVarName
}

func newSecretsDirEnv(newClient newClientFunc, dirPath string) *secretsDirEnv {
return &secretsDirEnv{
newClient: newClient,
dirPath: dirPath,
}
}

// EnvFlags defines environment variables sourced from command-line flags.
type EnvFlags map[string]string

Expand Down
Loading

0 comments on commit d5e091a

Please sign in to comment.