feat(credential): Add specific credential types (#1525)

This adds interfaces for the following credential types:
- Username and password
- Username and private key
- Certificate and private key

It also adds the following types for redacting secret data:
- Password
- PrivateKey
pull/1530/head
Michael Gaffney 5 years ago committed by GitHub
parent 2afac7348a
commit e9d3cebd45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -87,3 +87,31 @@ type Revoker interface {
// Revoke revokes the dynamic credentials issued for sessionid.
Revoke(ctx context.Context, sessionId string) error
}
// Password represents a secret password.
type Password string
// PrivateKey represents a secret private key.
type PrivateKey []byte
// UserPassword is a credential containing a username and a password.
type UserPassword interface {
Credential
Username() string
Password() Password
}
// KeyPair is a credential containing a username and a private key.
type KeyPair interface {
Credential
Username() string
Private() PrivateKey
}
// Certificate is a credential containing a certificate and the private key
// for the certificate.
type Certificate interface {
Credential
Certificate() []byte
Private() PrivateKey
}

@ -0,0 +1,39 @@
package credential
import "encoding/json"
const (
redactedPassword = "[REDACTED: password]"
redactedPrivateKey = "[REDACTED: private key]"
)
// String returns a string with the password redacted.
func (s Password) String() string {
return redactedPassword
}
// GoString returns a string with the password redacted.
func (s Password) GoString() string {
return redactedPassword
}
// MarshalJSON returns a JSON-encoded string with the password redacted.
func (s Password) MarshalJSON() ([]byte, error) {
return json.Marshal(redactedPassword)
}
// String returns a string with the private key redacted.
func (s PrivateKey) String() string {
return redactedPrivateKey
}
// GoString returns a string with the private key redacted.
func (s PrivateKey) GoString() string {
return redactedPrivateKey
}
// MarshalJSON returns a JSON-encoded byte slice with the private key
// redacted.
func (s PrivateKey) MarshalJSON() ([]byte, error) {
return json.Marshal([]byte(redactedPrivateKey))
}

@ -0,0 +1,132 @@
package credential
import (
"encoding/json"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPassword_String(t *testing.T) {
t.Parallel()
t.Run("redacted", func(t *testing.T) {
assert := assert.New(t)
const want = redactedPassword
passwd := Password("special secret")
assert.Equalf(want, passwd.String(), "Password.String() = %v, want %v", passwd.String(), want)
// Verify stringer is called
s := fmt.Sprintf("%s", passwd)
assert.Equalf(want, s, "Password.String() = %v, want %v", s, want)
})
}
func TestPassword_GoString(t *testing.T) {
t.Parallel()
t.Run("redacted", func(t *testing.T) {
assert := assert.New(t)
const want = redactedPassword
passwd := Password("magic secret")
assert.Equalf(want, passwd.GoString(), "Password.GoString() = %v, want %v", passwd.GoString(), want)
// Verify gostringer is called
s := fmt.Sprintf("%#v", passwd)
assert.Equalf(want, s, "Password.GoString() = %v, want %v", s, want)
})
}
func TestPassword_MarshalJSON(t *testing.T) {
t.Parallel()
t.Run("redacted", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
want, err := json.Marshal(redactedPassword)
require.NoError(err)
passwd := Password("normal secret")
got, err := passwd.MarshalJSON()
require.NoError(err)
assert.Equalf(want, got, "Password.MarshalJSON() = %s, want %s", got, want)
})
t.Run("within-struct", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
want := fmt.Sprintf(`%s`, redactedPassword)
type secretContainer struct {
P Password
S string
}
testB := "my secret"
secret := secretContainer{P: Password(testB), S: testB}
m, err := json.Marshal(secret)
require.NoError(err)
var sec secretContainer
err = json.Unmarshal(m, &sec)
require.NoError(err)
assert.Equal(Password(want), sec.P)
assert.Equal(testB, sec.S)
})
}
func TestPrivateKey_String(t *testing.T) {
t.Parallel()
t.Run("redacted", func(t *testing.T) {
assert := assert.New(t)
const want = redactedPrivateKey
pk := PrivateKey("special secret")
assert.Equalf(want, pk.String(), "PrivateKey.String() = %v, want %v", pk.String(), want)
// Verify stringer is called
s := fmt.Sprintf("%s", pk)
assert.Equalf(want, s, "PrivateKey.String() = %v, want %v", s, want)
})
}
func TestPrivateKey_GoString(t *testing.T) {
t.Parallel()
t.Run("redacted", func(t *testing.T) {
assert := assert.New(t)
const want = redactedPrivateKey
pk := PrivateKey("magic secret")
assert.Equalf(want, pk.GoString(), "PrivateKey.GoString() = %v, want %v", pk.GoString(), want)
// Verify gostringer is called
s := fmt.Sprintf("%#v", pk)
assert.Equalf(want, s, "PrivateKey.GoString() = %v, want %v", s, want)
})
}
func TestPrivateKey_MarshalJSON(t *testing.T) {
t.Parallel()
t.Run("redacted", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
want, err := json.Marshal([]byte(redactedPrivateKey))
require.NoError(err)
pk := PrivateKey("normal secret")
got, err := pk.MarshalJSON()
require.NoError(err)
assert.Equalf(want, got, "PrivateKey.MarshalJSON() = %s, want %s", got, want)
})
t.Run("within-struct", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
want := fmt.Sprintf(`%s`, redactedPrivateKey)
type secretContainer struct {
S PrivateKey
B []byte
}
testB := []byte("my secret")
secret := secretContainer{S: testB, B: testB}
m, err := json.Marshal(secret)
require.NoError(err)
var sec secretContainer
err = json.Unmarshal(m, &sec)
require.NoError(err)
assert.Equal(PrivateKey(want), sec.S)
assert.Equal(testB, sec.B)
})
}
Loading…
Cancel
Save