forked from TrueCloudLab/certificates
Remove now migrated sshutil.
This commit is contained in:
parent
e83e47a91e
commit
77624c6b1c
10 changed files with 0 additions and 1638 deletions
|
@ -1,104 +0,0 @@
|
|||
package sshutil
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.step.sm/crypto/randutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// Certificate is the json representation of ssh.Certificate.
|
||||
type Certificate struct {
|
||||
Nonce []byte `json:"nonce"`
|
||||
Key ssh.PublicKey `json:"-"`
|
||||
Serial uint64 `json:"serial"`
|
||||
Type CertType `json:"type"`
|
||||
KeyID string `json:"keyId"`
|
||||
Principals []string `json:"principals"`
|
||||
ValidAfter uint64 `json:"-"`
|
||||
ValidBefore uint64 `json:"-"`
|
||||
CriticalOptions map[string]string `json:"criticalOptions"`
|
||||
Extensions map[string]string `json:"extensions"`
|
||||
Reserved []byte `json:"reserved"`
|
||||
SignatureKey ssh.PublicKey `json:"-"`
|
||||
Signature *ssh.Signature `json:"-"`
|
||||
}
|
||||
|
||||
// NewCertificate creates a new certificate with the given key after parsing a
|
||||
// template given in the options.
|
||||
func NewCertificate(cr CertificateRequest, opts ...Option) (*Certificate, error) {
|
||||
o, err := new(Options).apply(cr, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if o.CertBuffer == nil {
|
||||
return nil, errors.New("certificate template cannot be empty")
|
||||
}
|
||||
|
||||
// With templates
|
||||
var cert Certificate
|
||||
if err := json.NewDecoder(o.CertBuffer).Decode(&cert); err != nil {
|
||||
return nil, errors.Wrap(err, "error unmarshaling certificate")
|
||||
}
|
||||
|
||||
// Complete with public key
|
||||
cert.Key = cr.Key
|
||||
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
func (c *Certificate) GetCertificate() *ssh.Certificate {
|
||||
return &ssh.Certificate{
|
||||
Nonce: c.Nonce,
|
||||
Key: c.Key,
|
||||
Serial: c.Serial,
|
||||
CertType: uint32(c.Type),
|
||||
KeyId: c.KeyID,
|
||||
ValidPrincipals: c.Principals,
|
||||
ValidAfter: c.ValidAfter,
|
||||
ValidBefore: c.ValidBefore,
|
||||
Permissions: ssh.Permissions{
|
||||
CriticalOptions: c.CriticalOptions,
|
||||
Extensions: c.Extensions,
|
||||
},
|
||||
Reserved: c.Reserved,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateCertificate signs the given certificate with the given signer. If the
|
||||
// certificate does not have a nonce or a serial, it will create random ones.
|
||||
func CreateCertificate(cert *ssh.Certificate, signer ssh.Signer) (*ssh.Certificate, error) {
|
||||
if len(cert.Nonce) == 0 {
|
||||
nonce, err := randutil.ASCII(32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert.Nonce = []byte(nonce)
|
||||
}
|
||||
|
||||
if cert.Serial == 0 {
|
||||
if err := binary.Read(rand.Reader, binary.BigEndian, &cert.Serial); err != nil {
|
||||
return nil, errors.Wrap(err, "error reading random number")
|
||||
}
|
||||
}
|
||||
|
||||
// Set signer public key.
|
||||
cert.SignatureKey = signer.PublicKey()
|
||||
|
||||
// Get bytes for signing trailing the signature length.
|
||||
data := cert.Marshal()
|
||||
data = data[:len(data)-4]
|
||||
|
||||
// Sign the certificate.
|
||||
sig, err := signer.Sign(rand.Reader, data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error signing certificate")
|
||||
}
|
||||
cert.Signature = sig
|
||||
|
||||
return cert, nil
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package sshutil
|
||||
|
||||
import "golang.org/x/crypto/ssh"
|
||||
|
||||
// CertificateRequests simulates a certificate request for SSH. SSH does not
|
||||
// have a concept of certificate requests, but the CA accepts the key and some
|
||||
// other parameters in the requests that are part of the certificate. This
|
||||
// struct will hold these parameters.
|
||||
//
|
||||
// CertificateRequests object will be used in the templates to set parameters
|
||||
// passed with the API instead of the validated ones.
|
||||
type CertificateRequest struct {
|
||||
Key ssh.PublicKey
|
||||
Type string
|
||||
KeyID string
|
||||
Principals []string
|
||||
}
|
|
@ -1,350 +0,0 @@
|
|||
package sshutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func mustGenerateKey(t *testing.T) (ssh.PublicKey, ssh.Signer) {
|
||||
t.Helper()
|
||||
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key, err := ssh.NewPublicKey(pub)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
signer, err := ssh.NewSignerFromKey(priv)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return key, signer
|
||||
}
|
||||
|
||||
func mustGeneratePublicKey(t *testing.T) ssh.PublicKey {
|
||||
t.Helper()
|
||||
key, _ := mustGenerateKey(t)
|
||||
return key
|
||||
}
|
||||
|
||||
func TestNewCertificate(t *testing.T) {
|
||||
key := mustGeneratePublicKey(t)
|
||||
cr := CertificateRequest{
|
||||
Key: key,
|
||||
}
|
||||
|
||||
type args struct {
|
||||
cr CertificateRequest
|
||||
opts []Option
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *Certificate
|
||||
wantErr bool
|
||||
}{
|
||||
{"user", args{cr, []Option{WithTemplate(DefaultCertificate, CreateTemplateData(UserCert, "jane@doe.com", []string{"jane"}))}}, &Certificate{
|
||||
Nonce: nil,
|
||||
Key: key,
|
||||
Serial: 0,
|
||||
Type: UserCert,
|
||||
KeyID: "jane@doe.com",
|
||||
Principals: []string{"jane"},
|
||||
ValidAfter: 0,
|
||||
ValidBefore: 0,
|
||||
CriticalOptions: nil,
|
||||
Extensions: map[string]string{
|
||||
"permit-X11-forwarding": "",
|
||||
"permit-agent-forwarding": "",
|
||||
"permit-port-forwarding": "",
|
||||
"permit-pty": "",
|
||||
"permit-user-rc": "",
|
||||
},
|
||||
Reserved: nil,
|
||||
SignatureKey: nil,
|
||||
Signature: nil,
|
||||
}, false},
|
||||
{"host", args{cr, []Option{WithTemplate(DefaultCertificate, CreateTemplateData(HostCert, "foobar", []string{"foo.internal", "bar.internal"}))}}, &Certificate{
|
||||
Nonce: nil,
|
||||
Key: key,
|
||||
Serial: 0,
|
||||
Type: HostCert,
|
||||
KeyID: "foobar",
|
||||
Principals: []string{"foo.internal", "bar.internal"},
|
||||
ValidAfter: 0,
|
||||
ValidBefore: 0,
|
||||
CriticalOptions: nil,
|
||||
Extensions: nil,
|
||||
Reserved: nil,
|
||||
SignatureKey: nil,
|
||||
Signature: nil,
|
||||
}, false},
|
||||
{"file", args{cr, []Option{WithTemplateFile("./testdata/github.tpl", TemplateData{
|
||||
TypeKey: UserCert,
|
||||
KeyIDKey: "john@doe.com",
|
||||
PrincipalsKey: []string{"john", "john@doe.com"},
|
||||
ExtensionsKey: DefaultExtensions(UserCert),
|
||||
InsecureKey: TemplateData{
|
||||
"User": map[string]interface{}{"username": "john"},
|
||||
},
|
||||
})}}, &Certificate{
|
||||
Nonce: nil,
|
||||
Key: key,
|
||||
Serial: 0,
|
||||
Type: UserCert,
|
||||
KeyID: "john@doe.com",
|
||||
Principals: []string{"john", "john@doe.com"},
|
||||
ValidAfter: 0,
|
||||
ValidBefore: 0,
|
||||
CriticalOptions: nil,
|
||||
Extensions: map[string]string{
|
||||
"login@github.com": "john",
|
||||
"permit-X11-forwarding": "",
|
||||
"permit-agent-forwarding": "",
|
||||
"permit-port-forwarding": "",
|
||||
"permit-pty": "",
|
||||
"permit-user-rc": "",
|
||||
},
|
||||
Reserved: nil,
|
||||
SignatureKey: nil,
|
||||
Signature: nil,
|
||||
}, false},
|
||||
{"base64", args{cr, []Option{WithTemplateBase64(base64.StdEncoding.EncodeToString([]byte(DefaultCertificate)), CreateTemplateData(HostCert, "foo.internal", nil))}}, &Certificate{
|
||||
Nonce: nil,
|
||||
Key: key,
|
||||
Serial: 0,
|
||||
Type: HostCert,
|
||||
KeyID: "foo.internal",
|
||||
Principals: nil,
|
||||
ValidAfter: 0,
|
||||
ValidBefore: 0,
|
||||
CriticalOptions: nil,
|
||||
Extensions: nil,
|
||||
Reserved: nil,
|
||||
SignatureKey: nil,
|
||||
Signature: nil,
|
||||
}, false},
|
||||
{"failNilOptions", args{cr, nil}, nil, true},
|
||||
{"failEmptyOptions", args{cr, nil}, nil, true},
|
||||
{"badBase64Template", args{cr, []Option{WithTemplateBase64("foobar", TemplateData{})}}, nil, true},
|
||||
{"badFileTemplate", args{cr, []Option{WithTemplateFile("./testdata/missing.tpl", TemplateData{})}}, nil, true},
|
||||
{"badJsonTemplate", args{cr, []Option{WithTemplate(`{"type":{{ .Type }}}`, TemplateData{})}}, nil, true},
|
||||
{"failTemplate", args{cr, []Option{WithTemplate(`{{ fail "an error" }}`, TemplateData{})}}, nil, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := NewCertificate(tt.args.cr, tt.args.opts...)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("NewCertificate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("NewCertificate() = \n%+v, want \n%+v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertificate_GetCertificate(t *testing.T) {
|
||||
key := mustGeneratePublicKey(t)
|
||||
|
||||
type fields struct {
|
||||
Nonce []byte
|
||||
Key ssh.PublicKey
|
||||
Serial uint64
|
||||
Type CertType
|
||||
KeyID string
|
||||
Principals []string
|
||||
ValidAfter uint64
|
||||
ValidBefore uint64
|
||||
CriticalOptions map[string]string
|
||||
Extensions map[string]string
|
||||
Reserved []byte
|
||||
SignatureKey ssh.PublicKey
|
||||
Signature *ssh.Signature
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want *ssh.Certificate
|
||||
}{
|
||||
{"user", fields{
|
||||
Nonce: []byte("0123456789"),
|
||||
Key: key,
|
||||
Serial: 123,
|
||||
Type: UserCert,
|
||||
KeyID: "key-id",
|
||||
Principals: []string{"john"},
|
||||
ValidAfter: 1111,
|
||||
ValidBefore: 2222,
|
||||
CriticalOptions: map[string]string{"foo": "bar"},
|
||||
Extensions: map[string]string{"login@github.com": "john"},
|
||||
Reserved: []byte("reserved"),
|
||||
SignatureKey: key,
|
||||
Signature: &ssh.Signature{Format: "foo", Blob: []byte("bar")},
|
||||
}, &ssh.Certificate{
|
||||
Nonce: []byte("0123456789"),
|
||||
Key: key,
|
||||
Serial: 123,
|
||||
CertType: ssh.UserCert,
|
||||
KeyId: "key-id",
|
||||
ValidPrincipals: []string{"john"},
|
||||
ValidAfter: 1111,
|
||||
ValidBefore: 2222,
|
||||
Permissions: ssh.Permissions{
|
||||
CriticalOptions: map[string]string{"foo": "bar"},
|
||||
Extensions: map[string]string{"login@github.com": "john"},
|
||||
},
|
||||
Reserved: []byte("reserved"),
|
||||
}},
|
||||
{"host", fields{
|
||||
Nonce: []byte("0123456789"),
|
||||
Key: key,
|
||||
Serial: 123,
|
||||
Type: HostCert,
|
||||
KeyID: "key-id",
|
||||
Principals: []string{"foo.internal", "bar.internal"},
|
||||
ValidAfter: 1111,
|
||||
ValidBefore: 2222,
|
||||
CriticalOptions: map[string]string{"foo": "bar"},
|
||||
Extensions: nil,
|
||||
Reserved: []byte("reserved"),
|
||||
SignatureKey: key,
|
||||
Signature: &ssh.Signature{Format: "foo", Blob: []byte("bar")},
|
||||
}, &ssh.Certificate{
|
||||
Nonce: []byte("0123456789"),
|
||||
Key: key,
|
||||
Serial: 123,
|
||||
CertType: ssh.HostCert,
|
||||
KeyId: "key-id",
|
||||
ValidPrincipals: []string{"foo.internal", "bar.internal"},
|
||||
ValidAfter: 1111,
|
||||
ValidBefore: 2222,
|
||||
Permissions: ssh.Permissions{
|
||||
CriticalOptions: map[string]string{"foo": "bar"},
|
||||
Extensions: nil,
|
||||
},
|
||||
Reserved: []byte("reserved"),
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Certificate{
|
||||
Nonce: tt.fields.Nonce,
|
||||
Key: tt.fields.Key,
|
||||
Serial: tt.fields.Serial,
|
||||
Type: tt.fields.Type,
|
||||
KeyID: tt.fields.KeyID,
|
||||
Principals: tt.fields.Principals,
|
||||
ValidAfter: tt.fields.ValidAfter,
|
||||
ValidBefore: tt.fields.ValidBefore,
|
||||
CriticalOptions: tt.fields.CriticalOptions,
|
||||
Extensions: tt.fields.Extensions,
|
||||
Reserved: tt.fields.Reserved,
|
||||
SignatureKey: tt.fields.SignatureKey,
|
||||
Signature: tt.fields.Signature,
|
||||
}
|
||||
if got := c.GetCertificate(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Certificate.GetCertificate() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateCertificate(t *testing.T) {
|
||||
key, signer := mustGenerateKey(t)
|
||||
type args struct {
|
||||
cert *ssh.Certificate
|
||||
signer ssh.Signer
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{"ok", args{&ssh.Certificate{
|
||||
Nonce: []byte("0123456789"),
|
||||
Key: key,
|
||||
Serial: 123,
|
||||
CertType: ssh.HostCert,
|
||||
KeyId: "foo",
|
||||
ValidPrincipals: []string{"foo.internal"},
|
||||
ValidAfter: 1111,
|
||||
ValidBefore: 2222,
|
||||
Permissions: ssh.Permissions{},
|
||||
Reserved: []byte("reserved"),
|
||||
}, signer}, false},
|
||||
{"emptyNonce", args{&ssh.Certificate{
|
||||
Key: key,
|
||||
Serial: 123,
|
||||
CertType: ssh.UserCert,
|
||||
KeyId: "jane@doe.com",
|
||||
ValidPrincipals: []string{"jane"},
|
||||
ValidAfter: 1111,
|
||||
ValidBefore: 2222,
|
||||
Permissions: ssh.Permissions{},
|
||||
Reserved: []byte("reserved"),
|
||||
}, signer}, false},
|
||||
{"emptySerial", args{&ssh.Certificate{
|
||||
Nonce: []byte("0123456789"),
|
||||
Key: key,
|
||||
CertType: ssh.UserCert,
|
||||
KeyId: "jane@doe.com",
|
||||
ValidPrincipals: []string{"jane"},
|
||||
ValidAfter: 1111,
|
||||
ValidBefore: 2222,
|
||||
Permissions: ssh.Permissions{},
|
||||
Reserved: []byte("reserved"),
|
||||
}, signer}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := CreateCertificate(tt.args.cert, tt.args.signer)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("CreateCertificate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != nil {
|
||||
switch {
|
||||
case len(got.Nonce) == 0:
|
||||
t.Errorf("CreateCertificate() nonce should not be empty")
|
||||
case got.Serial == 0:
|
||||
t.Errorf("CreateCertificate() serial should not be 0")
|
||||
case got.Signature == nil:
|
||||
t.Errorf("CreateCertificate() signature should not be nil")
|
||||
case !bytes.Equal(got.SignatureKey.Marshal(), tt.args.signer.PublicKey().Marshal()):
|
||||
t.Errorf("CreateCertificate() signature key is not the expected one")
|
||||
}
|
||||
|
||||
signature := got.Signature
|
||||
got.Signature = nil
|
||||
|
||||
data := got.Marshal()
|
||||
data = data[:len(data)-4]
|
||||
|
||||
sig, err := signer.Sign(rand.Reader, data)
|
||||
if err != nil {
|
||||
t.Errorf("signer.Sign() error = %v", err)
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
got.Signature = signature
|
||||
if err := signer.PublicKey().Verify(data, got.Signature); err != nil {
|
||||
t.Errorf("CreateCertificate() signature verify error = %v", err)
|
||||
}
|
||||
// Verify data with public key in cert
|
||||
if err := got.Verify(data, sig); err != nil {
|
||||
t.Errorf("CreateCertificate() certificate verify error = %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
package sshutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/cli/config"
|
||||
)
|
||||
|
||||
func getFuncMap(failMessage *string) template.FuncMap {
|
||||
m := sprig.TxtFuncMap()
|
||||
m["fail"] = func(msg string) (string, error) {
|
||||
*failMessage = msg
|
||||
return "", errors.New(msg)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Options are the options that can be passed to NewCertificate.
|
||||
type Options struct {
|
||||
CertBuffer *bytes.Buffer
|
||||
}
|
||||
|
||||
func (o *Options) apply(cr CertificateRequest, opts []Option) (*Options, error) {
|
||||
for _, fn := range opts {
|
||||
if err := fn(cr, o); err != nil {
|
||||
return o, err
|
||||
}
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// Option is the type used as a variadic argument in NewCertificate.
|
||||
type Option func(cr CertificateRequest, o *Options) error
|
||||
|
||||
// WithTemplate is an options that executes the given template text with the
|
||||
// given data.
|
||||
func WithTemplate(text string, data TemplateData) Option {
|
||||
return func(cr CertificateRequest, o *Options) error {
|
||||
terr := new(TemplateError)
|
||||
funcMap := getFuncMap(&terr.Message)
|
||||
|
||||
tmpl, err := template.New("template").Funcs(funcMap).Parse(text)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error parsing template")
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
data.SetCertificateRequest(cr)
|
||||
if err := tmpl.Execute(buf, data); err != nil {
|
||||
if terr.Message != "" {
|
||||
return terr
|
||||
}
|
||||
return errors.Wrapf(err, "error executing template")
|
||||
}
|
||||
o.CertBuffer = buf
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTemplateBase64 is an options that executes the given template base64
|
||||
// string with the given data.
|
||||
func WithTemplateBase64(s string, data TemplateData) Option {
|
||||
return func(cr CertificateRequest, o *Options) error {
|
||||
b, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error decoding template")
|
||||
}
|
||||
fn := WithTemplate(string(b), data)
|
||||
return fn(cr, o)
|
||||
}
|
||||
}
|
||||
|
||||
// WithTemplateFile is an options that reads the template file and executes it
|
||||
// with the given data.
|
||||
func WithTemplateFile(path string, data TemplateData) Option {
|
||||
return func(cr CertificateRequest, o *Options) error {
|
||||
filename := config.StepAbs(path)
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error reading %s", path)
|
||||
}
|
||||
fn := WithTemplate(string(b), data)
|
||||
return fn(cr, o)
|
||||
}
|
||||
}
|
|
@ -1,189 +0,0 @@
|
|||
package sshutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func Test_getFuncMap_fail(t *testing.T) {
|
||||
var failMesage string
|
||||
fns := getFuncMap(&failMesage)
|
||||
fail := fns["fail"].(func(s string) (string, error))
|
||||
s, err := fail("the fail message")
|
||||
if err == nil {
|
||||
t.Errorf("fail() error = %v, wantErr %v", err, errors.New("the fail message"))
|
||||
}
|
||||
if s != "" {
|
||||
t.Errorf("fail() = \"%s\", want \"the fail message\"", s)
|
||||
}
|
||||
if failMesage != "the fail message" {
|
||||
t.Errorf("fail() message = \"%s\", want \"the fail message\"", failMesage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithTemplate(t *testing.T) {
|
||||
key := mustGeneratePublicKey(t)
|
||||
cr := CertificateRequest{
|
||||
Key: key,
|
||||
}
|
||||
|
||||
type args struct {
|
||||
text string
|
||||
data TemplateData
|
||||
cr CertificateRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Options
|
||||
wantErr bool
|
||||
}{
|
||||
{"user", args{DefaultCertificate, TemplateData{
|
||||
TypeKey: "user",
|
||||
KeyIDKey: "jane@doe.com",
|
||||
PrincipalsKey: []string{"jane", "jane@doe.com"},
|
||||
ExtensionsKey: DefaultExtensions(UserCert),
|
||||
}, cr}, Options{
|
||||
CertBuffer: bytes.NewBufferString(`{
|
||||
"type": "user",
|
||||
"keyId": "jane@doe.com",
|
||||
"principals": ["jane","jane@doe.com"],
|
||||
"extensions": {"permit-X11-forwarding":"","permit-agent-forwarding":"","permit-port-forwarding":"","permit-pty":"","permit-user-rc":""},
|
||||
"criticalOptions": null
|
||||
}`)}, false},
|
||||
{"host", args{DefaultCertificate, TemplateData{
|
||||
TypeKey: "host",
|
||||
KeyIDKey: "foo",
|
||||
PrincipalsKey: []string{"foo.internal"},
|
||||
CriticalOptionsKey: map[string]string{"foo": "bar"},
|
||||
}, cr}, Options{
|
||||
CertBuffer: bytes.NewBufferString(`{
|
||||
"type": "host",
|
||||
"keyId": "foo",
|
||||
"principals": ["foo.internal"],
|
||||
"extensions": null,
|
||||
"criticalOptions": {"foo":"bar"}
|
||||
}`)}, false},
|
||||
{"fail", args{`{{ fail "a message" }}`, TemplateData{}, cr}, Options{}, true},
|
||||
{"failTemplate", args{`{{ fail "fatal error }}`, TemplateData{}, cr}, Options{}, true},
|
||||
{"error", args{`{{ mustHas 3 .Data }}`, TemplateData{
|
||||
"Data": 3,
|
||||
}, cr}, Options{}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var got Options
|
||||
fn := WithTemplate(tt.args.text, tt.args.data)
|
||||
if err := fn(tt.args.cr, &got); (err != nil) != tt.wantErr {
|
||||
t.Errorf("WithTemplate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("WithTemplate() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithTemplateBase64(t *testing.T) {
|
||||
key := mustGeneratePublicKey(t)
|
||||
cr := CertificateRequest{
|
||||
Key: key,
|
||||
}
|
||||
|
||||
type args struct {
|
||||
s string
|
||||
data TemplateData
|
||||
cr CertificateRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Options
|
||||
wantErr bool
|
||||
}{
|
||||
{"host", args{base64.StdEncoding.EncodeToString([]byte(DefaultCertificate)), TemplateData{
|
||||
TypeKey: "host",
|
||||
KeyIDKey: "foo.internal",
|
||||
PrincipalsKey: []string{"foo.internal", "bar.internal"},
|
||||
ExtensionsKey: map[string]interface{}{"foo": "bar"},
|
||||
CriticalOptionsKey: map[string]interface{}{"bar": "foo"},
|
||||
}, cr}, Options{
|
||||
CertBuffer: bytes.NewBufferString(`{
|
||||
"type": "host",
|
||||
"keyId": "foo.internal",
|
||||
"principals": ["foo.internal","bar.internal"],
|
||||
"extensions": {"foo":"bar"},
|
||||
"criticalOptions": {"bar":"foo"}
|
||||
}`)}, false},
|
||||
{"badBase64", args{"foobar", TemplateData{}, cr}, Options{}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var got Options
|
||||
fn := WithTemplateBase64(tt.args.s, tt.args.data)
|
||||
if err := fn(tt.args.cr, &got); (err != nil) != tt.wantErr {
|
||||
t.Errorf("WithTemplateBase64() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("WithTemplateBase64() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithTemplateFile(t *testing.T) {
|
||||
key := mustGeneratePublicKey(t)
|
||||
cr := CertificateRequest{
|
||||
Key: key,
|
||||
}
|
||||
|
||||
data := TemplateData{
|
||||
TypeKey: "user",
|
||||
KeyIDKey: "jane@doe.com",
|
||||
PrincipalsKey: []string{"jane", "jane@doe.com"},
|
||||
ExtensionsKey: DefaultExtensions(UserCert),
|
||||
InsecureKey: TemplateData{
|
||||
UserKey: map[string]interface{}{
|
||||
"username": "jane",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type args struct {
|
||||
path string
|
||||
data TemplateData
|
||||
cr CertificateRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Options
|
||||
wantErr bool
|
||||
}{
|
||||
{"github.com", args{"./testdata/github.tpl", data, cr}, Options{
|
||||
CertBuffer: bytes.NewBufferString(`{
|
||||
"type": "user",
|
||||
"keyId": "jane@doe.com",
|
||||
"principals": ["jane","jane@doe.com"],
|
||||
"extensions": {"login@github.com":"jane","permit-X11-forwarding":"","permit-agent-forwarding":"","permit-port-forwarding":"","permit-pty":"","permit-user-rc":""}
|
||||
}`),
|
||||
}, false},
|
||||
{"missing", args{"./testdata/missing.tpl", data, cr}, Options{}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var got Options
|
||||
fn := WithTemplateFile(tt.args.path, tt.args.data)
|
||||
if err := fn(tt.args.cr, &got); (err != nil) != tt.wantErr {
|
||||
t.Errorf("WithTemplateFile() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("WithTemplateFile() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,195 +0,0 @@
|
|||
package sshutil
|
||||
|
||||
// Variables used to hold template data.
|
||||
const (
|
||||
TypeKey = "Type"
|
||||
KeyIDKey = "KeyID"
|
||||
PrincipalsKey = "Principals"
|
||||
ExtensionsKey = "Extensions"
|
||||
CriticalOptionsKey = "CriticalOptions"
|
||||
TokenKey = "Token"
|
||||
InsecureKey = "Insecure"
|
||||
UserKey = "User"
|
||||
CertificateRequestKey = "CR"
|
||||
)
|
||||
|
||||
// TemplateError represents an error in a template produced by the fail
|
||||
// function.
|
||||
type TemplateError struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
// Error implements the error interface and returns the error string when a
|
||||
// template executes the `fail "message"` function.
|
||||
func (e *TemplateError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// TemplateData is an alias for map[string]interface{}. It represents the data
|
||||
// passed to the templates.
|
||||
type TemplateData map[string]interface{}
|
||||
|
||||
// CreateTemplateData returns a TemplateData with the given certificate type,
|
||||
// key id, principals, and the default extensions.
|
||||
func CreateTemplateData(ct CertType, keyID string, principals []string) TemplateData {
|
||||
return TemplateData{
|
||||
TypeKey: ct.String(),
|
||||
KeyIDKey: keyID,
|
||||
PrincipalsKey: principals,
|
||||
ExtensionsKey: DefaultExtensions(ct),
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultExtensions returns the default extensions set in an SSH certificate.
|
||||
func DefaultExtensions(ct CertType) map[string]interface{} {
|
||||
switch ct {
|
||||
case UserCert:
|
||||
return map[string]interface{}{
|
||||
"permit-X11-forwarding": "",
|
||||
"permit-agent-forwarding": "",
|
||||
"permit-port-forwarding": "",
|
||||
"permit-pty": "",
|
||||
"permit-user-rc": "",
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewTemplateData creates a new map for templates data.
|
||||
func NewTemplateData() TemplateData {
|
||||
return TemplateData{}
|
||||
}
|
||||
|
||||
// AddExtension adds one extension to the templates data.
|
||||
func (t TemplateData) AddExtension(key, value string) {
|
||||
if m, ok := t[ExtensionsKey].(map[string]interface{}); ok {
|
||||
m[key] = value
|
||||
} else {
|
||||
t[ExtensionsKey] = map[string]interface{}{
|
||||
key: value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddCriticalOption adds one critical option to the templates data.
|
||||
func (t TemplateData) AddCriticalOption(key, value string) {
|
||||
if m, ok := t[CriticalOptionsKey].(map[string]interface{}); ok {
|
||||
m[key] = value
|
||||
} else {
|
||||
t[CriticalOptionsKey] = map[string]interface{}{
|
||||
key: value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets a key-value pair in the template data.
|
||||
func (t TemplateData) Set(key string, v interface{}) {
|
||||
t[key] = v
|
||||
}
|
||||
|
||||
// SetInsecure sets a key-value pair in the insecure template data.
|
||||
func (t TemplateData) SetInsecure(key string, v interface{}) {
|
||||
if m, ok := t[InsecureKey].(TemplateData); ok {
|
||||
m[key] = v
|
||||
} else {
|
||||
t[InsecureKey] = TemplateData{key: v}
|
||||
}
|
||||
}
|
||||
|
||||
// SetType sets the certificate type in the template data.
|
||||
func (t TemplateData) SetType(typ CertType) {
|
||||
t.Set(TypeKey, typ.String())
|
||||
}
|
||||
|
||||
// SetKeyID sets the certificate key id in the template data.
|
||||
func (t TemplateData) SetKeyID(id string) {
|
||||
t.Set(KeyIDKey, id)
|
||||
}
|
||||
|
||||
// SetPrincipals sets the certificate principals in the template data.
|
||||
func (t TemplateData) SetPrincipals(p []string) {
|
||||
t.Set(PrincipalsKey, p)
|
||||
}
|
||||
|
||||
// SetExtensions sets the certificate extensions in the template data.
|
||||
func (t TemplateData) SetExtensions(e map[string]interface{}) {
|
||||
t.Set(ExtensionsKey, e)
|
||||
}
|
||||
|
||||
// SetCriticalOptions sets the certificate critical options in the template
|
||||
// data.
|
||||
func (t TemplateData) SetCriticalOptions(o map[string]interface{}) {
|
||||
t.Set(CriticalOptionsKey, o)
|
||||
}
|
||||
|
||||
// SetToken sets the given token in the template data.
|
||||
func (t TemplateData) SetToken(v interface{}) {
|
||||
t.Set(TokenKey, v)
|
||||
}
|
||||
|
||||
// SetUserData sets the given user provided object in the insecure template
|
||||
// data.
|
||||
func (t TemplateData) SetUserData(v interface{}) {
|
||||
t.SetInsecure(UserKey, v)
|
||||
}
|
||||
|
||||
// SetCertificateRequest sets the simulated ssh certificate request the insecure
|
||||
// template data.
|
||||
func (t TemplateData) SetCertificateRequest(cr CertificateRequest) {
|
||||
t.SetInsecure(CertificateRequestKey, cr)
|
||||
}
|
||||
|
||||
// DefaultCertificate is the default template for an SSH certificate.
|
||||
const DefaultCertificate = `{
|
||||
"type": "{{ .Type }}",
|
||||
"keyId": "{{ .KeyID }}",
|
||||
"principals": {{ toJson .Principals }},
|
||||
"extensions": {{ toJson .Extensions }},
|
||||
"criticalOptions": {{ toJson .CriticalOptions }}
|
||||
}`
|
||||
|
||||
// DefaultAdminCertificate is the template used by an admin user in a OIDC
|
||||
// provisioner.
|
||||
const DefaultAdminCertificate = `{
|
||||
"type": "{{ .Insecure.CR.Type }}",
|
||||
"keyId": "{{ .Insecure.CR.KeyID }}",
|
||||
"principals": {{ toJson .Insecure.CR.Principals }}
|
||||
{{- if eq .Insecure.CR.Type "user" }}
|
||||
, "extensions": {{ toJson .Extensions }},
|
||||
"criticalOptions": {{ toJson .CriticalOptions }}
|
||||
{{- end }}
|
||||
}`
|
||||
|
||||
// DefaultIIDCertificate is the default template for IID provisioners. By
|
||||
// default certificate type will be set always to host, key id to the instance
|
||||
// id. Principals will be only enforced by the provisioner if disableCustomSANs
|
||||
// is set to true.
|
||||
const DefaultIIDCertificate = `{
|
||||
"type": "{{ .Type }}",
|
||||
"keyId": "{{ .KeyID }}",
|
||||
{{- if .Insecure.CR.Principals }}
|
||||
"principals": {{ toJson .Insecure.CR.Principals }},
|
||||
{{- else }}
|
||||
"principals": {{ toJson .Principals }},
|
||||
{{- end }}
|
||||
"extensions": {{ toJson .Extensions }}
|
||||
}`
|
||||
|
||||
// CertificateRequestTemplate is the template used for provisioners that accepts
|
||||
// any certificate request. The provisioner must validate that type, keyId and
|
||||
// principals are passed in the request.
|
||||
const CertificateRequestTemplate = `{
|
||||
"type": "{{ .Insecure.CR.Type }}",
|
||||
"keyId": "{{ .Insecure.CR.KeyID }}",
|
||||
"principals": {{ toJson .Insecure.CR.Principals }}
|
||||
{{- if eq .Insecure.CR.Type "user" }}
|
||||
, "extensions": {
|
||||
"permit-X11-forwarding": "",
|
||||
"permit-agent-forwarding": "",
|
||||
"permit-port-forwarding": "",
|
||||
"permit-pty": "",
|
||||
"permit-user-rc": ""
|
||||
}
|
||||
{{- end }}
|
||||
}`
|
|
@ -1,481 +0,0 @@
|
|||
package sshutil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTemplateError_Error(t *testing.T) {
|
||||
type fields struct {
|
||||
Message string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
}{
|
||||
{"ok", fields{"message"}, "message"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
e := &TemplateError{
|
||||
Message: tt.fields.Message,
|
||||
}
|
||||
if got := e.Error(); got != tt.want {
|
||||
t.Errorf("TemplateError.Error() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTemplateData(t *testing.T) {
|
||||
type args struct {
|
||||
ct CertType
|
||||
keyID string
|
||||
principals []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want TemplateData
|
||||
}{
|
||||
{"user", args{UserCert, "john@doe.com", []string{"john", "john@doe.com"}}, TemplateData{
|
||||
TypeKey: "user",
|
||||
KeyIDKey: "john@doe.com",
|
||||
PrincipalsKey: []string{"john", "john@doe.com"},
|
||||
ExtensionsKey: map[string]interface{}{
|
||||
"permit-X11-forwarding": "",
|
||||
"permit-agent-forwarding": "",
|
||||
"permit-port-forwarding": "",
|
||||
"permit-pty": "",
|
||||
"permit-user-rc": "",
|
||||
},
|
||||
}},
|
||||
{"host", args{HostCert, "foo", []string{"foo.internal"}}, TemplateData{
|
||||
TypeKey: "host",
|
||||
KeyIDKey: "foo",
|
||||
PrincipalsKey: []string{"foo.internal"},
|
||||
ExtensionsKey: map[string]interface{}(nil),
|
||||
}},
|
||||
{"other", args{100, "foo", []string{"foo.internal"}}, TemplateData{
|
||||
TypeKey: "",
|
||||
KeyIDKey: "foo",
|
||||
PrincipalsKey: []string{"foo.internal"},
|
||||
ExtensionsKey: map[string]interface{}(nil),
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := CreateTemplateData(tt.args.ct, tt.args.keyID, tt.args.principals); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("CreateTemplateData() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultExtensions(t *testing.T) {
|
||||
type args struct {
|
||||
ct CertType
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want map[string]interface{}
|
||||
}{
|
||||
{"user", args{UserCert}, map[string]interface{}{
|
||||
"permit-X11-forwarding": "",
|
||||
"permit-agent-forwarding": "",
|
||||
"permit-port-forwarding": "",
|
||||
"permit-pty": "",
|
||||
"permit-user-rc": "",
|
||||
}},
|
||||
{"host", args{HostCert}, nil},
|
||||
{"other", args{100}, nil},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := DefaultExtensions(tt.args.ct); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("DefaultExtensions() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewTemplateData(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
want TemplateData
|
||||
}{
|
||||
{"ok", TemplateData{}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := NewTemplateData(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("NewTemplateData() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateData_AddExtension(t *testing.T) {
|
||||
type args struct {
|
||||
key string
|
||||
value string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
t TemplateData
|
||||
args args
|
||||
want TemplateData
|
||||
}{
|
||||
{"empty", TemplateData{}, args{"key", "value"}, TemplateData{
|
||||
ExtensionsKey: map[string]interface{}{"key": "value"},
|
||||
}},
|
||||
{"overwrite", TemplateData{
|
||||
ExtensionsKey: map[string]interface{}{"key": "value"},
|
||||
}, args{"key", "value"}, TemplateData{
|
||||
ExtensionsKey: map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
}},
|
||||
{"add", TemplateData{
|
||||
ExtensionsKey: map[string]interface{}{"foo": "bar"},
|
||||
}, args{"key", "value"}, TemplateData{
|
||||
ExtensionsKey: map[string]interface{}{
|
||||
"key": "value",
|
||||
"foo": "bar",
|
||||
},
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.t.AddExtension(tt.args.key, tt.args.value)
|
||||
if !reflect.DeepEqual(tt.t, tt.want) {
|
||||
t.Errorf("AddExtension() = %v, want %v", tt.t, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateData_AddCriticalOption(t *testing.T) {
|
||||
type args struct {
|
||||
key string
|
||||
value string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
t TemplateData
|
||||
args args
|
||||
want TemplateData
|
||||
}{
|
||||
{"empty", TemplateData{}, args{"key", "value"}, TemplateData{
|
||||
CriticalOptionsKey: map[string]interface{}{"key": "value"},
|
||||
}},
|
||||
{"overwrite", TemplateData{
|
||||
CriticalOptionsKey: map[string]interface{}{"key": "value"},
|
||||
}, args{"key", "value"}, TemplateData{
|
||||
CriticalOptionsKey: map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
}},
|
||||
{"add", TemplateData{
|
||||
CriticalOptionsKey: map[string]interface{}{"foo": "bar"},
|
||||
}, args{"key", "value"}, TemplateData{
|
||||
CriticalOptionsKey: map[string]interface{}{
|
||||
"key": "value",
|
||||
"foo": "bar",
|
||||
},
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.t.AddCriticalOption(tt.args.key, tt.args.value)
|
||||
if !reflect.DeepEqual(tt.t, tt.want) {
|
||||
t.Errorf("AddCriticalOption() = %v, want %v", tt.t, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateData_Set(t *testing.T) {
|
||||
type args struct {
|
||||
key string
|
||||
v interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
t TemplateData
|
||||
args args
|
||||
want TemplateData
|
||||
}{
|
||||
{"ok", TemplateData{}, args{"foo", "bar"}, TemplateData{
|
||||
"foo": "bar",
|
||||
}},
|
||||
{"overwrite", TemplateData{}, args{"foo", "bar"}, TemplateData{
|
||||
"foo": "bar",
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.t.Set(tt.args.key, tt.args.v)
|
||||
if !reflect.DeepEqual(tt.t, tt.want) {
|
||||
t.Errorf("Set() = %v, want %v", tt.t, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateData_SetInsecure(t *testing.T) {
|
||||
type args struct {
|
||||
key string
|
||||
v interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
td TemplateData
|
||||
args args
|
||||
want TemplateData
|
||||
}{
|
||||
{"empty", TemplateData{}, args{"foo", "bar"}, TemplateData{InsecureKey: TemplateData{"foo": "bar"}}},
|
||||
{"overwrite", TemplateData{InsecureKey: TemplateData{"foo": "bar"}}, args{"foo", "zar"}, TemplateData{InsecureKey: TemplateData{"foo": "zar"}}},
|
||||
{"add", TemplateData{InsecureKey: TemplateData{"foo": "bar"}}, args{"bar", "foo"}, TemplateData{InsecureKey: TemplateData{"foo": "bar", "bar": "foo"}}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.td.SetInsecure(tt.args.key, tt.args.v)
|
||||
if !reflect.DeepEqual(tt.td, tt.want) {
|
||||
t.Errorf("TemplateData.SetInsecure() = %v, want %v", tt.td, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateData_SetType(t *testing.T) {
|
||||
type args struct {
|
||||
typ CertType
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
t TemplateData
|
||||
args args
|
||||
want TemplateData
|
||||
}{
|
||||
{"user", TemplateData{}, args{UserCert}, TemplateData{
|
||||
TypeKey: "user",
|
||||
}},
|
||||
{"host", TemplateData{}, args{HostCert}, TemplateData{
|
||||
TypeKey: "host",
|
||||
}},
|
||||
{"overwrite", TemplateData{
|
||||
TypeKey: "host",
|
||||
}, args{UserCert}, TemplateData{
|
||||
TypeKey: "user",
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.t.SetType(tt.args.typ)
|
||||
if !reflect.DeepEqual(tt.t, tt.want) {
|
||||
t.Errorf("SetType() = %v, want %v", tt.t, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateData_SetKeyID(t *testing.T) {
|
||||
type args struct {
|
||||
id string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
t TemplateData
|
||||
args args
|
||||
want TemplateData
|
||||
}{
|
||||
{"ok", TemplateData{}, args{"key-id"}, TemplateData{
|
||||
KeyIDKey: "key-id",
|
||||
}},
|
||||
{"overwrite", TemplateData{}, args{"key-id-2"}, TemplateData{
|
||||
KeyIDKey: "key-id-2",
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.t.SetKeyID(tt.args.id)
|
||||
if !reflect.DeepEqual(tt.t, tt.want) {
|
||||
t.Errorf("SetKeyID() = %v, want %v", tt.t, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateData_SetPrincipals(t *testing.T) {
|
||||
type args struct {
|
||||
p []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
t TemplateData
|
||||
args args
|
||||
want TemplateData
|
||||
}{
|
||||
{"ok", TemplateData{}, args{[]string{"jane"}}, TemplateData{
|
||||
PrincipalsKey: []string{"jane"},
|
||||
}},
|
||||
{"overwrite", TemplateData{}, args{[]string{"john", "john@doe.com"}}, TemplateData{
|
||||
PrincipalsKey: []string{"john", "john@doe.com"},
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.t.SetPrincipals(tt.args.p)
|
||||
if !reflect.DeepEqual(tt.t, tt.want) {
|
||||
t.Errorf("SetPrincipals() = %v, want %v", tt.t, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateData_SetExtensions(t *testing.T) {
|
||||
type args struct {
|
||||
e map[string]interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
t TemplateData
|
||||
args args
|
||||
want TemplateData
|
||||
}{
|
||||
{"ok", TemplateData{}, args{map[string]interface{}{"foo": "bar"}}, TemplateData{
|
||||
ExtensionsKey: map[string]interface{}{"foo": "bar"},
|
||||
}},
|
||||
{"overwrite", TemplateData{
|
||||
ExtensionsKey: map[string]interface{}{"foo": "bar"},
|
||||
}, args{map[string]interface{}{"key": "value"}}, TemplateData{
|
||||
ExtensionsKey: map[string]interface{}{"key": "value"},
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.t.SetExtensions(tt.args.e)
|
||||
if !reflect.DeepEqual(tt.t, tt.want) {
|
||||
t.Errorf("SetExtensions() = %v, want %v", tt.t, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateData_SetCriticalOptions(t *testing.T) {
|
||||
type args struct {
|
||||
e map[string]interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
t TemplateData
|
||||
args args
|
||||
want TemplateData
|
||||
}{
|
||||
{"ok", TemplateData{}, args{map[string]interface{}{"foo": "bar"}}, TemplateData{
|
||||
CriticalOptionsKey: map[string]interface{}{"foo": "bar"},
|
||||
}},
|
||||
{"overwrite", TemplateData{
|
||||
CriticalOptionsKey: map[string]interface{}{"foo": "bar"},
|
||||
}, args{map[string]interface{}{"key": "value"}}, TemplateData{
|
||||
CriticalOptionsKey: map[string]interface{}{"key": "value"},
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.t.SetCriticalOptions(tt.args.e)
|
||||
if !reflect.DeepEqual(tt.t, tt.want) {
|
||||
t.Errorf("SetCriticalOptions() = %v, want %v", tt.t, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateData_SetToken(t *testing.T) {
|
||||
type args struct {
|
||||
v interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
td TemplateData
|
||||
args args
|
||||
want TemplateData
|
||||
}{
|
||||
{"ok", TemplateData{}, args{"token"}, TemplateData{TokenKey: "token"}},
|
||||
{"overwrite", TemplateData{TokenKey: "foo"}, args{"token"}, TemplateData{TokenKey: "token"}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.td.SetToken(tt.args.v)
|
||||
if !reflect.DeepEqual(tt.td, tt.want) {
|
||||
t.Errorf("TemplateData.SetToken() = %v, want %v", tt.td, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateData_SetUserData(t *testing.T) {
|
||||
type args struct {
|
||||
v interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
td TemplateData
|
||||
args args
|
||||
want TemplateData
|
||||
}{
|
||||
{"ok", TemplateData{}, args{"userData"}, TemplateData{InsecureKey: TemplateData{UserKey: "userData"}}},
|
||||
{"overwrite", TemplateData{InsecureKey: TemplateData{UserKey: "foo"}}, args{"userData"}, TemplateData{InsecureKey: TemplateData{UserKey: "userData"}}},
|
||||
{"existing", TemplateData{InsecureKey: TemplateData{"foo": "bar"}}, args{"userData"}, TemplateData{InsecureKey: TemplateData{"foo": "bar", UserKey: "userData"}}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.td.SetUserData(tt.args.v)
|
||||
if !reflect.DeepEqual(tt.td, tt.want) {
|
||||
t.Errorf("TemplateData.SetUserData() = %v, want %v", tt.td, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateData_SetCertificateRequest(t *testing.T) {
|
||||
cr1 := CertificateRequest{Key: mustGeneratePublicKey(t)}
|
||||
cr2 := CertificateRequest{Key: mustGeneratePublicKey(t)}
|
||||
type args struct {
|
||||
cr CertificateRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
t TemplateData
|
||||
args args
|
||||
want TemplateData
|
||||
}{
|
||||
{"ok", TemplateData{}, args{cr1}, TemplateData{
|
||||
InsecureKey: TemplateData{
|
||||
CertificateRequestKey: cr1,
|
||||
},
|
||||
}},
|
||||
{"overwrite", TemplateData{
|
||||
InsecureKey: TemplateData{
|
||||
UserKey: "data",
|
||||
CertificateRequestKey: cr1,
|
||||
},
|
||||
}, args{cr2}, TemplateData{
|
||||
InsecureKey: TemplateData{
|
||||
UserKey: "data",
|
||||
CertificateRequestKey: cr2,
|
||||
},
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.t.SetCertificateRequest(tt.args.cr)
|
||||
if !reflect.DeepEqual(tt.t, tt.want) {
|
||||
t.Errorf("TemplateData.SetCertificateRequest() = %v, want %v", tt.t, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
10
sshutil/testdata/github.tpl
vendored
10
sshutil/testdata/github.tpl
vendored
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"type": "{{ .Type }}",
|
||||
"keyId": "{{ .KeyID }}",
|
||||
"principals": {{ toJson .Principals }},
|
||||
{{- if .Insecure.User.username }}
|
||||
"extensions": {{ set .Extensions "login@github.com" .Insecure.User.username | toJson }}
|
||||
{{- else }}
|
||||
"extensions": {{ toJson .Extensions }}
|
||||
{{- end }}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
package sshutil
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// Hosts are tagged with k,v pairs. These tags are how a user is ultimately
|
||||
// associated with a host.
|
||||
type HostTag struct {
|
||||
ID string
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// Host defines expected attributes for an ssh host.
|
||||
type Host struct {
|
||||
HostID string `json:"hid"`
|
||||
HostTags []HostTag `json:"host_tags"`
|
||||
Hostname string `json:"hostname"`
|
||||
}
|
||||
|
||||
// CertType defines the certificate type, it can be a user or a host
|
||||
// certificate.
|
||||
type CertType uint32
|
||||
|
||||
const (
|
||||
// UserCert defines a user certificate.
|
||||
UserCert CertType = ssh.UserCert
|
||||
|
||||
// HostCert defines a host certificate.
|
||||
HostCert CertType = ssh.HostCert
|
||||
)
|
||||
|
||||
const (
|
||||
userString = "user"
|
||||
hostString = "host"
|
||||
)
|
||||
|
||||
// CertTypeFromString returns the CertType for the string "user" and "host".
|
||||
func CertTypeFromString(s string) (CertType, error) {
|
||||
switch strings.ToLower(s) {
|
||||
case userString:
|
||||
return UserCert, nil
|
||||
case hostString:
|
||||
return HostCert, nil
|
||||
default:
|
||||
return 0, errors.Errorf("unknown certificate type '%s'", s)
|
||||
}
|
||||
}
|
||||
|
||||
// String returns "user" for user certificates and "host" for host certificates.
|
||||
// It will return the empty string for any other value.
|
||||
func (c CertType) String() string {
|
||||
switch c {
|
||||
case UserCert:
|
||||
return userString
|
||||
case HostCert:
|
||||
return hostString
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface for CertType. UserCert
|
||||
// will be marshaled as the string "user" and HostCert as "host".
|
||||
func (c CertType) MarshalJSON() ([]byte, error) {
|
||||
if s := c.String(); s != "" {
|
||||
return []byte(`"` + s + `"`), nil
|
||||
}
|
||||
return nil, errors.Errorf("unknown certificate type %d", c)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface for CertType.
|
||||
func (c *CertType) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return errors.Wrap(err, "error unmarshaling certificate type")
|
||||
}
|
||||
certType, err := CertTypeFromString(s)
|
||||
if err != nil {
|
||||
return errors.Errorf("error unmarshaling '%s' as a certificate type", s)
|
||||
}
|
||||
*c = certType
|
||||
return nil
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
package sshutil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCertTypeFromString(t *testing.T) {
|
||||
type args struct {
|
||||
s string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want CertType
|
||||
wantErr bool
|
||||
}{
|
||||
{"user", args{"user"}, UserCert, false},
|
||||
{"USER", args{"USER"}, UserCert, false},
|
||||
{"host", args{"host"}, HostCert, false},
|
||||
{"Host", args{"Host"}, HostCert, false},
|
||||
{" user ", args{" user "}, 0, true},
|
||||
{"invalid", args{"invalid"}, 0, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := CertTypeFromString(tt.args.s)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("CertTypeFromString() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("CertTypeFromString() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertType_String(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
c CertType
|
||||
want string
|
||||
}{
|
||||
{"user", UserCert, "user"},
|
||||
{"host", HostCert, "host"},
|
||||
{"empty", 100, ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.c.String(); got != tt.want {
|
||||
t.Errorf("CertType.String() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertType_MarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
c CertType
|
||||
want []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{"user", UserCert, []byte(`"user"`), false},
|
||||
{"host", HostCert, []byte(`"host"`), false},
|
||||
{"error", 100, nil, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.c.MarshalJSON()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("CertType.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("CertType.MarshalJSON() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertType_UnmarshalJSON(t *testing.T) {
|
||||
type args struct {
|
||||
data []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want CertType
|
||||
wantErr bool
|
||||
}{
|
||||
{"user", args{[]byte(`"user"`)}, UserCert, false},
|
||||
{"USER", args{[]byte(`"USER"`)}, UserCert, false},
|
||||
{"host", args{[]byte(`"host"`)}, HostCert, false},
|
||||
{"HosT", args{[]byte(`"HosT"`)}, HostCert, false},
|
||||
{" user ", args{[]byte(`" user "`)}, 0, true},
|
||||
{"number", args{[]byte(`1`)}, 0, true},
|
||||
{"object", args{[]byte(`{}`)}, 0, true},
|
||||
{"badJSON", args{[]byte(`"user`)}, 0, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var ct CertType
|
||||
if err := ct.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
|
||||
t.Errorf("CertType.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if !reflect.DeepEqual(ct, tt.want) {
|
||||
t.Errorf("CertType.UnmarshalJSON() = %v, want %v", ct, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue