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

AWS polish #117

Merged
merged 36 commits into from
Aug 28, 2019
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
58b8906
Implement configdir package to handle location of configuration files
jpcoenen Aug 27, 2019
49c7397
Implement new credentials.Creator interface
jpcoenen Aug 27, 2019
2a96e57
Remove unused function
jpcoenen Aug 27, 2019
f2fafdf
Use readKey() in UseKey()
jpcoenen Aug 27, 2019
3408dbf
Use a custom Reader interface for reading credentials
jpcoenen Aug 27, 2019
9adb23a
Move finding default credentials to Client
jpcoenen Aug 27, 2019
0a18f68
Handle cases where no passphrase is given
jpcoenen Aug 27, 2019
30a1b0f
Fix tests
jpcoenen Aug 27, 2019
c214dbc
Add GoDoc to configdir package
mackenbach Aug 27, 2019
f6f2d76
Add godoc to WithConfigDir option
mackenbach Aug 27, 2019
25755ca
Add/improve godoc for credential readers
mackenbach Aug 27, 2019
12df21a
Add package godoc for credentials package
mackenbach Aug 27, 2019
7e47dfd
Add/amend comments for credential creators
mackenbach Aug 27, 2019
6d1d24b
Add/improve secrethub.Client godoc
mackenbach Aug 27, 2019
231e49d
Add godoc for Client.DefaultCredential
mackenbach Aug 27, 2019
27e6a00
Run gofmt
mackenbach Aug 27, 2019
ca1e134
Let configdir.Dir implement Stringer interface
jpcoenen Aug 27, 2019
6dd978e
Extract setting options on Client to separate function
jpcoenen Aug 27, 2019
99a643e
Initialize ConfigDir to nil
jpcoenen Aug 27, 2019
bd89778
Remove readKey() in favour of already existing ImportKey()
jpcoenen Aug 27, 2019
fba9d15
Rearrange functions for clarity and remove duplication
jpcoenen Aug 27, 2019
e2d6d28
Make function clearer
jpcoenen Aug 27, 2019
4400bc8
Export error for non-existing credential file
jpcoenen Aug 27, 2019
11f0cda
Set filemode to previously used defaults
jpcoenen Aug 27, 2019
8a84569
Fix missing/wrong comments
jpcoenen Aug 27, 2019
0e84a45
Use bytes functions instead of strings
jpcoenen Aug 27, 2019
a035e0b
Add missing comments
jpcoenen Aug 27, 2019
b399490
Merge remote-tracking branch 'origin/feature/aws-integration-merge' i…
SimonBarendse Aug 28, 2019
9a15196
Rename encodeCredentialPartsToString => encodeCredentialParts
SimonBarendse Aug 28, 2019
7191415
Change encodeCredentialParts test to expect bytes
SimonBarendse Aug 28, 2019
d909589
Make path in configdir private
jpcoenen Aug 28, 2019
737803b
Retry getting passphrase if none is provided
jpcoenen Aug 28, 2019
06bbef3
Combine Fingerprint and Verifier in Export method
SimonBarendse Aug 28, 2019
c3e4ef9
Rename Export() to Encode() for keys
jpcoenen Aug 28, 2019
72be34a
Fix incorrect comment
jpcoenen Aug 28, 2019
c2bfdd3
Merge pull request #120 from secrethub/feature/verifier-export
jpcoenen Aug 28, 2019
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
69 changes: 56 additions & 13 deletions pkg/secrethub/client.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package secrethub

import (
"os"

"github.com/secrethub/secrethub-go/internals/api"
"github.com/secrethub/secrethub-go/internals/crypto"
"github.com/secrethub/secrethub-go/internals/errio"
"github.com/secrethub/secrethub-go/pkg/secrethub/configdir"
"github.com/secrethub/secrethub-go/pkg/secrethub/credentials"
"github.com/secrethub/secrethub-go/pkg/secrethub/internals/http"
)
Expand All @@ -14,14 +17,23 @@ const (

// ClientAdapter is an interface that can be used to consume the SecretHub client and is implemented by secrethub.Client.
type ClientAdapter interface {
// AccessRules returns a service used to manage access rules.
AccessRules() AccessRuleService
// Accounts returns a service used to manage SecretHub accounts.
Accounts() AccountService
// Dirs returns a service used to manage directories.
Dirs() DirService
// Me returns a service used to manage the current authenticated account.
Me() MeService
// Orgs returns a service used to manage shared organization workspaces.
Orgs() OrgService
// Repos returns a service used to manage repositories.
Repos() RepoService
// Secrets returns a service used to manage secrets.
Secrets() SecretService
// Services returns a service used to manage non-human service accounts.
Services() ServiceService
// Users returns a service used to manage (human) user accounts.
Users() UserService
}

Expand All @@ -47,7 +59,8 @@ type Client struct {
// These are cached
repoIndexKeys map[api.RepoPath]*crypto.SymmetricKey

appInfo *AppInfo
appInfo *AppInfo
ConfigDir *configdir.Dir
}

// AppInfo contains information about the application that is using the SecretHub client.
Expand Down Expand Up @@ -81,16 +94,23 @@ func NewClient(with ...ClientOption) (*Client, error) {
httpClient: http.NewClient(),
repoIndexKeys: make(map[api.RepoPath]*crypto.SymmetricKey),
}
for _, option := range with {
err := option(client)
err := client.with(with...)
if err != nil {
return nil, err
}

// ConfigDir should be fully initialized before loading any default credentials.
if client.ConfigDir == nil {
configDir, err := configdir.Default()
if err != nil {
return nil, err
}
client.ConfigDir = configDir
}

// Try to use default key credentials if none provided explicitly
if client.decrypter == nil {
err := WithCredentials(credentials.UseKey(nil, nil))(client)
err := client.with(WithCredentials(credentials.UseKey(client.DefaultCredential())))
// nolint: staticcheck
if err != nil {
// TODO: log that default credential was not loaded.
SimonBarendse marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -122,47 +142,70 @@ func Must(c *Client, err error) *Client {
return c
}

// AccessRules returns an AccessRuleService.
// AccessRules returns a service used to manage access rules.
func (c *Client) AccessRules() AccessRuleService {
return newAccessRuleService(c)
}

// Accounts returns an AccountService.
// Accounts returns a service used to manage SecretHub accounts.
func (c *Client) Accounts() AccountService {
return newAccountService(c)
}

// Dirs returns an DirService.
// Dirs returns a service used to manage directories.
func (c *Client) Dirs() DirService {
return newDirService(c)
}

// Me returns a MeService.
// Me returns a service used to manage the current authenticated account.
func (c *Client) Me() MeService {
return newMeService(c)
}

// Orgs returns an OrgService.
// Orgs returns a service used to manage shared organization workspaces.
func (c *Client) Orgs() OrgService {
return newOrgService(c)
}

// Repos returns an RepoService.
// Repos returns a service used to manage repositories.
func (c *Client) Repos() RepoService {
return newRepoService(c)
}

// Secrets returns an SecretService.
// Secrets returns a service used to manage secrets.
func (c *Client) Secrets() SecretService {
return newSecretService(c)
}

// Services returns an ServiceService.
// Services returns a service used to manage non-human service accounts.
func (c *Client) Services() ServiceService {
return newServiceService(c)
}

// Users returns an UserService.
// Users returns a service used to manage (human) user accounts.
func (c *Client) Users() UserService {
return newUserService(c)
}

// with applies ClientOptions to a Client. Should only be called during initialization.
func (c *Client) with(options ...ClientOption) error {
for _, o := range options {
err := o(c)
if err != nil {
return err
}
}
return nil
}

// DefaultCredential returns a reader pointing to the configured credential,
// sourcing it either from the SECRETHUB_CREDENTIAL environment variable or
// from the configuration directory.
func (c *Client) DefaultCredential() credentials.Reader {
envCredential := os.Getenv("SECRETHUB_CREDENTIAL")
if envCredential != "" {
return credentials.FromString(envCredential)
}

return c.ConfigDir.Credential()
}
9 changes: 9 additions & 0 deletions pkg/secrethub/client_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"
"time"

"github.com/secrethub/secrethub-go/pkg/secrethub/configdir"
"github.com/secrethub/secrethub-go/pkg/secrethub/credentials"
httpclient "github.com/secrethub/secrethub-go/pkg/secrethub/internals/http"
)
Expand Down Expand Up @@ -47,6 +48,14 @@ func WithAppInfo(appInfo *AppInfo) ClientOption {
}
}

// WithConfigDir sets the configuration directory to use (among others) for sourcing the credential file from.
func WithConfigDir(configDir configdir.Dir) ClientOption {
mackenbach marked this conversation as resolved.
Show resolved Hide resolved
return func(c *Client) error {
c.ConfigDir = &configDir
return nil
}
}

// WithCredentials sets the credential to be used for authenticating to the API and decrypting the account key.
func WithCredentials(provider credentials.Provider) ClientOption {
return func(c *Client) error {
Expand Down
89 changes: 89 additions & 0 deletions pkg/secrethub/configdir/dir.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Package configdir provides simple functions to manage the SecretHub
// configuration directory.
package configdir
mackenbach marked this conversation as resolved.
Show resolved Hide resolved

import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"

"github.com/mitchellh/go-homedir"
)

var (
// ErrCredentialNotFound is returned when a credential file does not exist but CredentialFile.Read() is called.
ErrCredentialNotFound = errors.New("credential not found")
)

// Dir represents the configuration directory located at some path
// on the file system.
type Dir struct {
Path string
SimonBarendse marked this conversation as resolved.
Show resolved Hide resolved
}

// Default is the default way to get the location of the SecretHub
// configuration directory, sourcing it from the environment variable
// SECRETHUB_CONFIG_DIR or falling back to the ~/.secrethub directory.
func Default() (*Dir, error) {
envDir := os.Getenv("SECRETHUB_CONFIG_DIR")
if envDir != "" {
return &Dir{
Path: envDir,
}, nil
}

homeDir, err := homedir.Dir()
if err != nil {
return &Dir{}, fmt.Errorf("cannot get home directory: %v", err)
}
return &Dir{
Path: filepath.Join(homeDir, ".secrethub"),
}, nil
}

// Credential returns the file that contains the SecretHub API credential.
func (c Dir) Credential() *CredentialFile {
return &CredentialFile{
Path: filepath.Join(c.Path, "credential"),
}
}

func (c Dir) String() string {
return c.Path
}

// CredentialFile represents the file that contains the SecretHub API credential.
// By default, it's a file named "credential" in the configuration directory.
type CredentialFile struct {
Path string
}

// Write writes the given bytes to the credential file.
func (f *CredentialFile) Write(data []byte) error {
err := os.MkdirAll(filepath.Dir(f.Path), os.FileMode(0700))
if err != nil {
return err
}
return ioutil.WriteFile(f.Path, data, os.FileMode(0600))
}

// Exists returns true when a file exists at the path this credential points to.
func (f *CredentialFile) Exists() bool {
if _, err := os.Stat(f.Path); os.IsNotExist(err) {
return false
}
return true
}

// Read reads from the filesystem and returns the contents of the credential file.
func (f *CredentialFile) Read() ([]byte, error) {
file, err := os.Open(f.Path)
if os.IsNotExist(err) {
return nil, ErrCredentialNotFound
} else if err != nil {
return nil, err
}
return ioutil.ReadAll(file)
}
92 changes: 53 additions & 39 deletions pkg/secrethub/credentials/creators.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package credentials

import (
"errors"
"net/http"

"github.com/secrethub/secrethub-go/internals/auth"
"github.com/secrethub/secrethub-go/internals/aws"
"github.com/secrethub/secrethub-go/internals/crypto"

Expand All @@ -13,12 +9,14 @@ import (

// Creator is an interface is accepted by functions that need a new credential to be created.
type Creator interface {
Create() (Verifier, Encrypter, map[string]string, error)
}

// KeyCreator is used to create a new key-based credential.
type KeyCreator struct {
key *RSACredential
// Create creates the actual credential (e.g. by generating a key).
Create() error
// Verifier returns information that the server can use to verify a request authenticated with the credential.
Verifier() Verifier
// Encrypter returns a wrapper that is used to encrypt data, typically an account key.
Encrypter() Encrypter
// Metadata returns a set of metadata about the credential. The result can be empty if no metadata is provided.
Metadata() map[string]string
}

// CreateKey returns a Creator that creates a key based credential.
Expand All @@ -29,29 +27,24 @@ func CreateKey() *KeyCreator {
return &KeyCreator{}
}

// KeyCreator is used to create a new key-based credential.
type KeyCreator struct {
Key
}

// Create generates a new key and stores it in the KeyCreator.
func (c *KeyCreator) Create() (Verifier, Encrypter, map[string]string, error) {
func (kc *KeyCreator) Create() error {
key, err := GenerateRSACredential(crypto.RSAKeyLength)
if err != nil {
return nil, nil, nil, err
}
c.key = key
return c.key, c.key, map[string]string{}, nil
}

// Export the key of this credential to string format to save for later use.
// This can only be called after Create() is executed, for example by secrethub.UserService.Create([...])
// or secrethub.ServiceService.Create([...])
func (c *KeyCreator) Export() (string, error) {
if c.key == nil {
return "", errors.New("key has not yet been generated created. Use KeyCreator before calling Export()")
return err
}
return EncodeCredential(c.key)
kc.key = key
return nil
}

// Provide returns a credential that can be used for authentication and decryption.
func (c *KeyCreator) Provide(*http.Client) (auth.Authenticator, Decrypter, error) {
return c.key, c.key, nil
// Metadata returns a set of metadata associated with this credential.
func (kc *KeyCreator) Metadata() map[string]string {
return map[string]string{}
}

// CreateAWS returns a Creator that creates an AWS-based credential.
Expand All @@ -61,19 +54,40 @@ func (c *KeyCreator) Provide(*http.Client) (auth.Authenticator, Decrypter, error
// awsCfg can be used to optionally configure the used AWS client. For example to set the region.
// The KMS key id and role are returned in the credentials metadata.
func CreateAWS(kmsKeyID string, roleARN string, awsCfg ...*awssdk.Config) Creator {
return creatorFunc(func() (Verifier, Encrypter, map[string]string, error) {
creator, metadata, err := aws.NewCredentialCreator(kmsKeyID, roleARN, awsCfg...)
if err != nil {
return nil, nil, nil, err
}
return creator, creator, metadata, nil
})
return &awsCreator{
kmsKeyID: kmsKeyID,
roleARN: roleARN,
awsCfg: awsCfg,
}
}

type awsCreator struct {
mackenbach marked this conversation as resolved.
Show resolved Hide resolved
kmsKeyID string
roleARN string
awsCfg []*awssdk.Config

credentialCreator *aws.CredentialCreator
metadata map[string]string
}

// creatorFunc is a helper type that can transform any func() (Verifier, Encrypter, map[string]string, error) into a Creator.
type creatorFunc func() (Verifier, Encrypter, map[string]string, error)
func (ac *awsCreator) Create() error {
creator, metadata, err := aws.NewCredentialCreator(ac.kmsKeyID, ac.roleARN, ac.awsCfg...)
if err != nil {
return err
}
ac.credentialCreator = creator
ac.metadata = metadata
return nil
}

func (ac *awsCreator) Verifier() Verifier {
return ac.credentialCreator
}

func (ac *awsCreator) Encrypter() Encrypter {
return ac.credentialCreator
}

// Create is implemented to let creatorFunc implement the Creator interface.
func (f creatorFunc) Create() (Verifier, Encrypter, map[string]string, error) {
return f()
func (ac *awsCreator) Metadata() map[string]string {
return ac.metadata
}
1 change: 1 addition & 0 deletions pkg/secrethub/credentials/credentials.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package credentials provides utilities for managing SecretHub API credentials.
package credentials

import "github.com/secrethub/secrethub-go/internals/api"
Expand Down
Loading