Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/thycotic/dsv-cli into pub…
Browse files Browse the repository at this point in the history
…lic-master
  • Loading branch information
thycotic-rd committed Dec 8, 2020
2 parents 533da42 + 478e865 commit decdb7f
Show file tree
Hide file tree
Showing 99 changed files with 5,107 additions and 1,190 deletions.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,3 @@ inittests/winenv/
inittests/localvars/
inittests/testdata/
inittests/.defaultvars

.env.sh
25 changes: 9 additions & 16 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
BUILD = $(shell date +%Y%m%d%H%M)
VERSION = $(shell git describe --always --dirty)

PKGNAME = thy
PKGNAME = dsv
ifneq ($(CONSTANTS_CLINAME),)
PKGNAME = $(CONSTANTS_CLINAME)
endif
Expand All @@ -14,20 +14,13 @@ else
ifeq ($(shell uname), Linux)
GOOS = linux
THEN = &&
EXE_SUFFIX =
EXE_SUFFIX =
endif
endif

LDFLAGS = -X thy/version.Version=$(VERSION) -X thy/version.Build=$(BUILD)
LDFLAGS_REL := $(LDFLAGS) -s -w

PACKAGES=`go list ./... | grep -v fake`

fmt:
for pkg in ${PACKAGES}; do \
go fmt $$pkg; \
done;

clean:
$(shell rm -rf bin)

Expand All @@ -38,7 +31,7 @@ build:
GOOS=$(GOOS) GOARCH=$(GOARCH) GO111MODULE=on go build -ldflags="$(LDFLAGS)" -o $(PKGNAME)$(EXE_SUFFIX)

build-test:
GOOS=$(GOOS) GOARCH=$(GOARCH) GO111MODULE=on go test -c -covermode=count -coverpkg ./...
GOOS=$(GOOS) GOARCH=$(GOARCH) GO111MODULE=on go test -c -covermode=count -coverpkg ./... -o $(PKGNAME)$(EXE_SUFFIX).test

build-release:
GOOS=$(GOOS) GOARCH=$(GOARCH) GO111MODULE=on go build -ldflags="$(LDFLAGS_REL)" -o $(PKGNAME)$(EXE_SUFFIX)
Expand All @@ -55,13 +48,13 @@ create-checksum:
$(shell cd bin/$(VERSION); for file in *; do sha256sum $$file > $$file-sha256.txt; done)

TEMPLATE = '{"latest":"$(VERSION)","links":\
{"darwin/amd64":"https://dsv.thycotic.com/downloads/cli/$(VERSION)/thy-darwin-x64",\
"linux/amd64":"https://dsv.thycotic.com/downloads/cli/$(VERSION)/thy-linux-x64",\
"linux/386":"https://dsv.thycotic.com/downloads/cli/$(VERSION)/thy-linux-x86",\
"windows/amd64":"https://dsv.thycotic.com/downloads/cli/$(VERSION)/thy-win-x64.exe",\
"windows/386":"https://dsv.thycotic.com/downloads/cli/$(VERSION)/thy-win-x86.exe"}}'
{"darwin/amd64":"https://dsv.thycotic.com/downloads/cli/$(VERSION)/$(PKGNAME)-darwin-x64",\
"linux/amd64":"https://dsv.thycotic.com/downloads/cli/$(VERSION)/$(PKGNAME)-linux-x64",\
"linux/386":"https://dsv.thycotic.com/downloads/cli/$(VERSION)/$(PKGNAME)-linux-x86",\
"windows/amd64":"https://dsv.thycotic.com/downloads/cli/$(VERSION)/$(PKGNAME)-win-x64.exe",\
"windows/386":"https://dsv.thycotic.com/downloads/cli/$(VERSION)/$(PKGNAME)-win-x86.exe"}}'

capture-latest-version:
$(shell echo $(TEMPLATE) > bin/cli-version.json)

.DEFAULT_GOAL := build
.DEFAULT_GOAL := build
13 changes: 6 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
![Go](https://github.com/thycotic/dsv-cli/workflows/Go/badge.svg)
# Thycotic DevOps Secrets Vault CLI

Thycotic DevOps Secrets Vault CLI is an automation tool for the management and access of secret information.
Expand Down Expand Up @@ -28,11 +27,11 @@ To install:
```
go get -u github.com/posener/complete/gocomplete
gocomplete -install
thy -install
dsv -install
```
To uninstall:
```
thy -uninstall
dsv -uninstall
gocomplete -uninstall
```

Expand Down Expand Up @@ -67,7 +66,7 @@ Create a secret at the path resources/us-east-1/server1
```bash


thy secret create --path resources/us-east-1/server1 --data "{
dsv secret create --path resources/us-east-1/server1 --data "{
"data": {"foo":"test"},
"description": "foo secret",
"attributes": {
Expand All @@ -78,7 +77,7 @@ thy secret create --path resources/us-east-1/server1 --data "{
Or, as a shortcut for secret commands, the first two arguments are interpereted as --path and --data. Additional flags should follow
```bash

thy secret update resources/us-east-1/server1 --data "{
dsv secret update resources/us-east-1/server1 --data "{
"data": {"foo":"test"},
"description": "foo secret",
"attributes": {
Expand All @@ -88,7 +87,7 @@ thy secret update resources/us-east-1/server1 --data "{
```
Read a secret field
```bash
thy secret read resources/us-east-1/server1 -bf .data.foo
dsv secret read resources/us-east-1/server1 -bf .data.foo
```


Expand Down Expand Up @@ -161,4 +160,4 @@ We use [counterfeiter](https://github.com/maxbrunsfeld/counterfeiter) to generat
* **Thycotic Software** - [Thycotic](https://thycotic.com)

## License
See LICENSE file.
See LICENSE file.
153 changes: 145 additions & 8 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,37 @@ import (
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"reflect"
"runtime"
"strings"
"time"

"thy/paths"

"github.com/thycotic-rd/cli"

config "thy/cli-config"
cst "thy/constants"
"thy/errors"
"thy/requests"
"thy/store"
"thy/utils"

"github.com/Azure/go-autorest/autorest"
azure "github.com/Azure/go-autorest/autorest/azure/auth"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/thycotic-rd/go-autorest/autorest"
azure "github.com/thycotic-rd/go-autorest/autorest/azure/auth"
"github.com/thycotic-rd/viper"
"github.com/pkg/browser"
"github.com/spf13/viper"
)

const (
leewaySecondsTokenExp = 10
refreshTokenLifeSeconds = 60 * 60 * 48
refreshTokenLifeSeconds = 60 * 60 * 720
)

// Note that this global error variable is of type *ApiError, not regular error.
Expand All @@ -49,6 +56,7 @@ const (
FederatedAws = AuthType("aws")
FederatedAzure = AuthType("azure")
FederatedGcp = AuthType("gcp")
Oidc = AuthType("oidc")
)

// GetTokenKey gets the key for the auth type
Expand Down Expand Up @@ -116,6 +124,12 @@ func NewAuthenticator(store store.Store, client requests.Client) Authenticator {
}

func (a *authenticator) GetToken() (*TokenResponse, *errors.ApiError) {
if AuthType(viper.GetString(cst.AuthType)) == ClientCredential && (strings.TrimSpace(viper.GetString(cst.AuthClientID)) != "" || strings.TrimSpace(viper.GetString(cst.AuthClientSecret)) != "") {
return a.GetTokenCacheOverride(false)
}
if AuthType(viper.GetString(cst.AuthType)) == Password && strings.TrimSpace(viper.GetString(cst.Password)) != "" {
return a.GetTokenCacheOverride(false)
}
return a.GetTokenCacheOverride(true)
}

Expand Down Expand Up @@ -159,18 +173,24 @@ func (a *authenticator) getTokenForAuthType(at AuthType, useCache bool) (*TokenR
if keySuffix == "" {
keySuffix = cst.DefaultProfile
}
} else if at == Oidc {
profile := viper.GetString(cst.Profile)
keySuffix = viper.GetString(keyName)
if profile != "" && profile != cst.DefaultProfile {
keySuffix = fmt.Sprintf("%s-%s", keySuffix, profile)
}
} else {
keySuffix = viper.GetString(keyName)
}

keyToken := at.GetTokenKey(keySuffix)

if useCache && strings.TrimSpace(viper.GetString(cst.Password)) == "" {
if useCache {
if err := a.store.Get(keyToken, &tr); err != nil {
return nil, err
} else if tr != nil && !tr.IsNil() {
// If init (cli-config) or config, invalidate existing token and later re-authenticate.
if cmd := viper.GetString(cst.MainCommand); cmd == cst.NounCliConfig || cmd == cst.NounConfig {
if cmd := viper.GetString(cst.MainCommand); cmd == cst.NounCliConfig {
err := a.store.Wipe(keyToken)
if err != nil {
return nil, err
Expand Down Expand Up @@ -274,6 +294,10 @@ func (a *authenticator) getTokenForAuthType(at AuthType, useCache bool) (*TokenR
}
data.RefreshToken = refreshToken
}
} else if at == Oidc {
data.Provider = viper.GetString(cst.AuthProvider)
data.CallbackHost = viper.GetString(cst.Callback)
data.CallbackUrl = fmt.Sprintf("http://%s/callback", viper.GetString(cst.Callback))
}
}

Expand Down Expand Up @@ -305,12 +329,78 @@ func (a *authenticator) getTokenForAuthType(at AuthType, useCache bool) (*TokenR
}
}

type RedirectResponse struct {
RedirectUrl string `json:"redirect_url"`
}

type AuthResponse struct {
state string
authCode string
message string
err *errors.ApiError
}

func (a *authenticator) fetchTokenVault(at AuthType, data requestBody) (*TokenResponse, *errors.ApiError) {
var response TokenResponse
if err := data.ValidateForAuthType(at); err != nil {
return nil, errors.New(err)
}
uri := utils.CreateURI(cst.NounToken, nil)
if at == Oidc {
ui := cli.BasicUi{
Writer: os.Stdout,
Reader: os.Stdin,
ErrorWriter: os.Stderr,
}

var redirectResponse RedirectResponse
uri := paths.CreateURI("oidc/auth", nil)

if err := a.requestClient.DoRequestOut("POST", uri, data, &redirectResponse); err != nil {
return nil, err
}
callbackListener, err := net.Listen("tcp", data.CallbackHost)
if err != nil {
return nil, errors.NewF("unable to open callback listener: %v", err)
}
defer callbackListener.Close()

authChannel := make(chan AuthResponse)
http.HandleFunc("/callback", a.handleOidcAuth(authChannel))

go func() {
err := http.Serve(callbackListener, nil)
if err != nil && err != http.ErrServerClosed {
authChannel <- AuthResponse{
message: "login failed",
err: errors.New(err),
}
}
}()

browserOpened := false
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
if err = browser.OpenURL(redirectResponse.RedirectUrl); err == nil {
browserOpened = true
}
}
if !browserOpened {
ui.Info(fmt.Sprintf("Unable to open browser, complete login process here:\n %s", redirectResponse.RedirectUrl))
}

select {
case ar := <-authChannel:
if ar.err != nil {
return nil, ar.err
}
data.State = ar.state
data.AuthorizationCode = ar.authCode
ui.Info(fmt.Sprintf("Received response from oidc provider, submitting authorization code to %s", cst.ProductName))
case <-time.After(5 * time.Minute):
ui.Info(fmt.Sprintf("Timeout occurred waiting for callback from oidc provider"))
return nil, errors.NewS("no callback occurred after redirect")
}
}
uri := paths.CreateURI(cst.NounToken, nil)
if err := a.requestClient.DoRequestOut("POST", uri, data, &response); err != nil {
return nil, err
} else if response.IsNil() {
Expand All @@ -320,6 +410,41 @@ func (a *authenticator) fetchTokenVault(at AuthType, data requestBody) (*TokenRe
return &response, nil
}

//
func (a *authenticator) handleOidcAuth(doneCh chan<- AuthResponse) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {

b, err := ioutil.ReadAll(req.Body)
if err != nil {
w.Write([]byte(err.Error()))
doneCh <- AuthResponse{
err: errors.New(err),
message: "error in callback",
}
}

code := req.URL.Query().Get("code")
state := req.URL.Query().Get("state")

if code == "" || state == "" {
doneCh <- AuthResponse{
err: errors.NewS("missing values in callback, authorization code or state are empty"),
}
w.Write(b)
return
}

w.Write([]byte(youDidIt))
doneCh <- AuthResponse{
err: nil,
message: "success",
authCode: code,
state: state,
}

}
}

// setupDataForPasswordAuth prepares the requestBody object with data for password auth.
// It should be used in the authentication workflow, not on its own.
func setupDataForPasswordAuth(data *requestBody) error {
Expand All @@ -339,7 +464,7 @@ func setupDataForPasswordAuth(data *requestBody) error {
if pass, err := config.GetSecureSetting(passSetting); err == nil && pass != "" {
if passSetting == cst.SecurePassword {
keyPath := GetEncryptionKeyFilename(viper.GetString(cst.Tenant), userName)
key, err := store.ReadFile(keyPath)
key, err := store.ReadFileInDefaultPath(keyPath)
if err != nil || key == "" {
return KeyfileNotFoundError
}
Expand Down Expand Up @@ -392,6 +517,10 @@ type requestBody struct {
AwsHeaders string `json:"aws_headers"`
Jwt string `json:"jwt"`
AzureAuthClientID string
AuthorizationCode string `json:"authorization_code"`
CallbackUrl string `json:"callback_url"`
State string `json:"state"`
CallbackHost string `json:"_"`
}

type TokenResponse struct {
Expand Down Expand Up @@ -442,6 +571,7 @@ var authTypeToGrantType = map[AuthType]string{
FederatedAws: "aws_iam",
FederatedAzure: "azure",
FederatedGcp: "gcp",
Oidc: "oidc",
}

type paramSpec struct {
Expand Down Expand Up @@ -553,4 +683,11 @@ var paramSpecDict = map[AuthType][]paramSpec{
RequestVar: false,
},
},
Oidc: {
{PropName: "AuthType",
ArgName: cst.AuthType,
IsKey: true,
RequestVar: false,
},
},
}
Loading

0 comments on commit decdb7f

Please sign in to comment.