You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
smallstep-certificates/authority/provisioner/nebula_test.go

964 lines
27 KiB
Go

package provisioner
import (
"context"
"crypto"
"crypto/ed25519"
"crypto/rand"
"crypto/x509"
"net"
"net/url"
"reflect"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/slackhq/nebula/cert"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/randutil"
"go.step.sm/crypto/x25519"
"go.step.sm/crypto/x509util"
"golang.org/x/crypto/ssh"
)
func mustNebulaIPNet(t *testing.T, s string) *net.IPNet {
t.Helper()
ip, ipNet, err := net.ParseCIDR(s)
if err != nil {
t.Fatal(err)
}
if ip = ip.To4(); ip == nil {
t.Fatalf("nebula only supports ipv4, have %s", s)
}
ipNet.IP = ip
return ipNet
}
func mustNebulaCA(t *testing.T) (*cert.NebulaCertificate, ed25519.PrivateKey) {
t.Helper()
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
nc := &cert.NebulaCertificate{
Details: cert.NebulaCertificateDetails{
Name: "TestCA",
Groups: []string{"test"},
Ips: []*net.IPNet{
mustNebulaIPNet(t, "10.1.0.0/16"),
},
Subnets: []*net.IPNet{},
NotBefore: time.Now(),
NotAfter: time.Now().Add(10 * time.Minute),
PublicKey: pub,
IsCA: true,
},
}
if err := nc.Sign(priv); err != nil {
t.Fatal(err)
}
return nc, priv
}
func mustNebulaCert(t *testing.T, name string, ipNet *net.IPNet, groups []string, ca *cert.NebulaCertificate, signer ed25519.PrivateKey) (*cert.NebulaCertificate, crypto.Signer) {
t.Helper()
pub, priv, err := x25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
issuer, err := ca.Sha256Sum()
if err != nil {
t.Fatal(err)
}
invertedGroups := make(map[string]struct{}, len(groups))
for _, name := range groups {
invertedGroups[name] = struct{}{}
}
t1 := time.Now().Truncate(time.Second)
nc := &cert.NebulaCertificate{
Details: cert.NebulaCertificateDetails{
Name: name,
Ips: []*net.IPNet{ipNet},
Subnets: []*net.IPNet{},
Groups: groups,
NotBefore: t1,
NotAfter: t1.Add(5 * time.Minute),
PublicKey: pub,
IsCA: false,
Issuer: issuer,
InvertedGroups: invertedGroups,
},
}
if err := nc.Sign(signer); err != nil {
t.Fatal(err)
}
return nc, priv
}
func mustNebulaProvisioner(t *testing.T) (*Nebula, *cert.NebulaCertificate, ed25519.PrivateKey) {
t.Helper()
nc, signer := mustNebulaCA(t)
ncPem, err := nc.MarshalToPEM()
if err != nil {
t.Fatal(err)
}
bTrue := true
p := &Nebula{
Type: TypeNebula.String(),
Name: "nebulous",
Roots: ncPem,
Claims: &Claims{
EnableSSHCA: &bTrue,
},
}
if err := p.Init(Config{
Claims: globalProvisionerClaims,
Audiences: testAudiences,
}); err != nil {
t.Fatal(err)
}
return p, nc, signer
}
func mustNebulaToken(t *testing.T, sub, iss, aud string, iat time.Time, sans []string, nc *cert.NebulaCertificate, key crypto.Signer) string {
t.Helper()
ncDer, err := nc.Marshal()
if err != nil {
t.Fatal(err)
}
so := new(jose.SignerOptions)
so.WithType("JWT")
so.WithHeader(NebulaCertHeader, ncDer)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.XEdDSA, Key: key}, so)
if err != nil {
t.Fatal(err)
}
id, err := randutil.ASCII(64)
if err != nil {
t.Fatal(err)
}
claims := struct {
jose.Claims
SANS []string `json:"sans"`
}{
Claims: jose.Claims{
ID: id,
Subject: sub,
Issuer: iss,
IssuedAt: jose.NewNumericDate(iat),
NotBefore: jose.NewNumericDate(iat),
Expiry: jose.NewNumericDate(iat.Add(5 * time.Minute)),
Audience: []string{aud},
},
SANS: sans,
}
tok, err := jose.Signed(sig).Claims(claims).CompactSerialize()
if err != nil {
t.Fatal(err)
}
return tok
}
func mustNebulaSSHToken(t *testing.T, sub, iss, aud string, iat time.Time, opts *SignSSHOptions, nc *cert.NebulaCertificate, key crypto.Signer) string {
t.Helper()
ncDer, err := nc.Marshal()
if err != nil {
t.Fatal(err)
}
so := new(jose.SignerOptions)
so.WithType("JWT")
so.WithHeader(NebulaCertHeader, ncDer)
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.XEdDSA, Key: key}, so)
if err != nil {
t.Fatal(err)
}
id, err := randutil.ASCII(64)
if err != nil {
t.Fatal(err)
}
claims := struct {
jose.Claims
Step *stepPayload `json:"step,omitempty"`
}{
Claims: jose.Claims{
ID: id,
Subject: sub,
Issuer: iss,
IssuedAt: jose.NewNumericDate(iat),
NotBefore: jose.NewNumericDate(iat),
Expiry: jose.NewNumericDate(iat.Add(5 * time.Minute)),
Audience: []string{aud},
},
}
if opts != nil {
claims.Step = &stepPayload{
SSH: opts,
}
}
tok, err := jose.Signed(sig).Claims(claims).CompactSerialize()
if err != nil {
t.Fatal(err)
}
return tok
}
func TestNebula_Init(t *testing.T) {
nc, _ := mustNebulaCA(t)
ncPem, err := nc.MarshalToPEM()
if err != nil {
t.Fatal(err)
}
cfg := Config{
Claims: globalProvisionerClaims,
Audiences: testAudiences,
}
type fields struct {
Type string
Name string
Roots []byte
Claims *Claims
Options *Options
}
type args struct {
config Config
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{"ok", fields{"Nebula", "Nebulous", ncPem, nil, nil}, args{cfg}, false},
{"ok with claims", fields{"Nebula", "Nebulous", ncPem, &Claims{DefaultTLSDur: &Duration{Duration: time.Hour}}, nil}, args{cfg}, false},
{"ok with options", fields{"Nebula", "Nebulous", ncPem, nil, &Options{X509: &X509Options{Template: x509util.DefaultLeafTemplate}}}, args{cfg}, false},
{"fail type", fields{"", "Nebulous", ncPem, nil, nil}, args{cfg}, true},
{"fail name", fields{"Nebula", "", ncPem, nil, nil}, args{cfg}, true},
{"fail root", fields{"Nebula", "Nebulous", nil, nil, nil}, args{cfg}, true},
{"fail bad root", fields{"Nebula", "Nebulous", ncPem[:16], nil, nil}, args{cfg}, true},
{"fail bad claims", fields{"Nebula", "Nebulous", ncPem, &Claims{
MinTLSDur: &Duration{Duration: 0},
}, nil}, args{cfg}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &Nebula{
Type: tt.fields.Type,
Name: tt.fields.Name,
Roots: tt.fields.Roots,
Claims: tt.fields.Claims,
Options: tt.fields.Options,
}
if err := p.Init(tt.args.config); (err != nil) != tt.wantErr {
t.Errorf("Nebula.Init() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestNebula_GetID(t *testing.T) {
type fields struct {
ID string
Name string
}
tests := []struct {
name string
fields fields
want string
}{
{"ok with id", fields{"1234", "nebulous"}, "1234"},
{"ok with name", fields{"", "nebulous"}, "nebula/nebulous"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &Nebula{
ID: tt.fields.ID,
Name: tt.fields.Name,
}
if got := p.GetID(); got != tt.want {
t.Errorf("Nebula.GetID() = %v, want %v", got, tt.want)
}
})
}
}
func TestNebula_GetIDForToken(t *testing.T) {
type fields struct {
Name string
}
tests := []struct {
name string
fields fields
want string
}{
{"ok", fields{"nebulous"}, "nebula/nebulous"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &Nebula{
Name: tt.fields.Name,
}
if got := p.GetIDForToken(); got != tt.want {
t.Errorf("Nebula.GetIDForToken() = %v, want %v", got, tt.want)
}
})
}
}
func TestNebula_GetTokenID(t *testing.T) {
p, ca, signer := mustNebulaProvisioner(t)
c1, priv := mustNebulaCert(t, "test.lan", mustNebulaIPNet(t, "10.1.0.1/16"), []string{"group"}, ca, signer)
t1 := mustNebulaToken(t, "test.lan", p.Name, p.ctl.Audiences.Sign[0], now(), []string{"test.lan", "10.1.0.1"}, c1, priv)
_, claims, err := parseToken(t1)
if err != nil {
t.Fatal(err)
}
type args struct {
token string
}
tests := []struct {
name string
p *Nebula
args args
want string
wantErr bool
}{
{"ok", p, args{t1}, claims.ID, false},
{"fail parse", p, args{"token"}, "", true},
{"fail claims", p, args{func() string {
parts := strings.Split(t1, ".")
return parts[0] + ".eyIifQ." + parts[1]
}()}, "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.p.GetTokenID(tt.args.token)
if (err != nil) != tt.wantErr {
t.Errorf("Nebula.GetTokenID() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Nebula.GetTokenID() = %v, want %v", got, tt.want)
}
})
}
}
func TestNebula_GetName(t *testing.T) {
type fields struct {
Name string
}
tests := []struct {
name string
fields fields
want string
}{
{"ok", fields{"nebulous"}, "nebulous"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &Nebula{
Name: tt.fields.Name,
}
if got := p.GetName(); got != tt.want {
t.Errorf("Nebula.GetName() = %v, want %v", got, tt.want)
}
})
}
}
func TestNebula_GetType(t *testing.T) {
type fields struct {
Type string
}
tests := []struct {
name string
fields fields
want Type
}{
{"ok", fields{"Nebula"}, TypeNebula},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &Nebula{
Type: tt.fields.Type,
}
if got := p.GetType(); got != tt.want {
t.Errorf("Nebula.GetType() = %v, want %v", got, tt.want)
}
})
}
}
func TestNebula_GetEncryptedKey(t *testing.T) {
tests := []struct {
name string
p *Nebula
wantKid string
wantKey string
wantOk bool
}{
{"ok", &Nebula{}, "", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotKid, gotKey, gotOk := tt.p.GetEncryptedKey()
if gotKid != tt.wantKid {
t.Errorf("Nebula.GetEncryptedKey() gotKid = %v, want %v", gotKid, tt.wantKid)
}
if gotKey != tt.wantKey {
t.Errorf("Nebula.GetEncryptedKey() gotKey = %v, want %v", gotKey, tt.wantKey)
}
if gotOk != tt.wantOk {
t.Errorf("Nebula.GetEncryptedKey() gotOk = %v, want %v", gotOk, tt.wantOk)
}
})
}
}
func TestNebula_AuthorizeSign(t *testing.T) {
ctx := context.TODO()
p, ca, signer := mustNebulaProvisioner(t)
crt, priv := mustNebulaCert(t, "test.lan", mustNebulaIPNet(t, "10.1.0.1/16"), []string{"test"}, ca, signer)
ok := mustNebulaToken(t, "test.lan", p.Name, p.ctl.Audiences.Sign[0], now(), []string{"test.lan", "10.1.0.1"}, crt, priv)
okNoSANs := mustNebulaToken(t, "test.lan", p.Name, p.ctl.Audiences.Sign[0], now(), nil, crt, priv)
pBadOptions, _, _ := mustNebulaProvisioner(t)
pBadOptions.caPool = p.caPool
pBadOptions.Options = &Options{
X509: &X509Options{
TemplateData: []byte(`{""}`),
},
}
type args struct {
ctx context.Context
token string
}
tests := []struct {
name string
p *Nebula
args args
wantErr bool
}{
{"ok", p, args{ctx, ok}, false},
{"ok no sans", p, args{ctx, okNoSANs}, false},
{"fail token", p, args{ctx, "token"}, true},
{"fail template", pBadOptions, args{ctx, ok}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := tt.p.AuthorizeSign(tt.args.ctx, tt.args.token)
if (err != nil) != tt.wantErr {
t.Errorf("Nebula.AuthorizeSign() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
func TestNebula_AuthorizeSSHSign(t *testing.T) {
ctx := context.TODO()
// Ok provisioner
p, ca, signer := mustNebulaProvisioner(t)
crt, priv := mustNebulaCert(t, "test.lan", mustNebulaIPNet(t, "10.1.0.1/16"), []string{"test"}, ca, signer)
ok := mustNebulaSSHToken(t, "test.lan", p.Name, p.ctl.Audiences.SSHSign[0], now(), &SignSSHOptions{
CertType: "host",
KeyID: "test.lan",
Principals: []string{"test.lan", "10.1.0.1"},
}, crt, priv)
okNoOptions := mustNebulaSSHToken(t, "test.lan", p.Name, p.ctl.Audiences.SSHSign[0], now(), nil, crt, priv)
okWithValidity := mustNebulaSSHToken(t, "test.lan", p.Name, p.ctl.Audiences.SSHSign[0], now(), &SignSSHOptions{
ValidAfter: NewTimeDuration(now().Add(1 * time.Hour)),
ValidBefore: NewTimeDuration(now().Add(10 * time.Hour)),
}, crt, priv)
failUserCert := mustNebulaSSHToken(t, "test.lan", p.Name, p.ctl.Audiences.SSHSign[0], now(), &SignSSHOptions{
CertType: "user",
}, crt, priv)
failPrincipals := mustNebulaSSHToken(t, "test.lan", p.Name, p.ctl.Audiences.SSHSign[0], now(), &SignSSHOptions{
CertType: "host",
KeyID: "test.lan",
Principals: []string{"test.lan", "10.1.0.1", "foo.bar"},
}, crt, priv)
// Provisioner with SSH disabled
var bFalse bool
pDisabled, _, _ := mustNebulaProvisioner(t)
pDisabled.caPool = p.caPool
pDisabled.Claims.EnableSSHCA = &bFalse
// Provisioner with bad templates
pBadOptions, _, _ := mustNebulaProvisioner(t)
pBadOptions.caPool = p.caPool
pBadOptions.Options = &Options{
SSH: &SSHOptions{
TemplateData: []byte(`{""}`),
},
}
type args struct {
ctx context.Context
token string
}
tests := []struct {
name string
p *Nebula
args args
wantErr bool
}{
{"ok", p, args{ctx, ok}, false},
{"ok no options", p, args{ctx, okNoOptions}, false},
{"ok with validity", p, args{ctx, okWithValidity}, false},
{"fail token", p, args{ctx, "token"}, true},
{"fail user", p, args{ctx, failUserCert}, true},
{"fail principals", p, args{ctx, failPrincipals}, true},
{"fail disabled", pDisabled, args{ctx, ok}, true},
{"fail template", pBadOptions, args{ctx, ok}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := tt.p.AuthorizeSSHSign(tt.args.ctx, tt.args.token)
if (err != nil) != tt.wantErr {
t.Errorf("Nebula.AuthorizeSSHSign() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
func TestNebula_AuthorizeRenew(t *testing.T) {
ctx := context.TODO()
now := time.Now().Truncate(time.Second)
// Ok provisioner
p, _, _ := mustNebulaProvisioner(t)
// Provisioner with renewal disabled
bTrue := true
pDisabled, _, _ := mustNebulaProvisioner(t)
pDisabled.Claims.DisableRenewal = &bTrue
type args struct {
ctx context.Context
crt *x509.Certificate
}
tests := []struct {
name string
p *Nebula
args args
wantErr bool
}{
{"ok", p, args{ctx, &x509.Certificate{
NotBefore: now,
NotAfter: now.Add(time.Hour),
}}, false},
{"fail disabled", pDisabled, args{ctx, &x509.Certificate{
NotBefore: now,
NotAfter: now.Add(time.Hour),
}}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.p.AuthorizeRenew(tt.args.ctx, tt.args.crt); (err != nil) != tt.wantErr {
t.Errorf("Nebula.AuthorizeRenew() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestNebula_AuthorizeRevoke(t *testing.T) {
ctx := context.TODO()
// Ok provisioner
p, ca, signer := mustNebulaProvisioner(t)
crt, priv := mustNebulaCert(t, "test.lan", mustNebulaIPNet(t, "10.1.0.1/16"), []string{"test"}, ca, signer)
ok := mustNebulaToken(t, "test.lan", p.Name, p.ctl.Audiences.Revoke[0], now(), nil, crt, priv)
// Fail different CA
nc, signer := mustNebulaCA(t)
crt, priv = mustNebulaCert(t, "test.lan", mustNebulaIPNet(t, "10.1.0.1/16"), []string{"test"}, nc, signer)
failToken := mustNebulaToken(t, "test.lan", p.Name, p.ctl.Audiences.Revoke[0], now(), nil, crt, priv)
type args struct {
ctx context.Context
token string
}
tests := []struct {
name string
p *Nebula
args args
wantErr bool
}{
{"ok", p, args{ctx, ok}, false},
{"fail token", p, args{ctx, failToken}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.p.AuthorizeRevoke(tt.args.ctx, tt.args.token); (err != nil) != tt.wantErr {
t.Errorf("Nebula.AuthorizeRevoke() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestNebula_AuthorizeSSHRevoke(t *testing.T) {
ctx := context.TODO()
// Ok provisioner
p, ca, signer := mustNebulaProvisioner(t)
crt, priv := mustNebulaCert(t, "test.lan", mustNebulaIPNet(t, "10.1.0.1/16"), []string{"test"}, ca, signer)
ok := mustNebulaSSHToken(t, "test.lan", p.Name, p.ctl.Audiences.SSHRevoke[0], now(), nil, crt, priv)
// Fail different CA
nc, signer := mustNebulaCA(t)
crt, priv = mustNebulaCert(t, "test.lan", mustNebulaIPNet(t, "10.1.0.1/16"), []string{"test"}, nc, signer)
failToken := mustNebulaSSHToken(t, "test.lan", p.Name, p.ctl.Audiences.SSHRevoke[0], now(), nil, crt, priv)
// Provisioner with SSH disabled
var bFalse bool
pDisabled, _, _ := mustNebulaProvisioner(t)
pDisabled.caPool = p.caPool
pDisabled.Claims.EnableSSHCA = &bFalse
type args struct {
ctx context.Context
token string
}
tests := []struct {
name string
p *Nebula
args args
wantErr bool
}{
{"ok", p, args{ctx, ok}, false},
{"fail token", p, args{ctx, failToken}, true},
{"fail disabled", pDisabled, args{ctx, ok}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.p.AuthorizeSSHRevoke(tt.args.ctx, tt.args.token); (err != nil) != tt.wantErr {
t.Errorf("Nebula.AuthorizeSSHRevoke() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestNebula_AuthorizeSSHRenew(t *testing.T) {
p, ca, signer := mustNebulaProvisioner(t)
crt, priv := mustNebulaCert(t, "test.lan", mustNebulaIPNet(t, "10.1.0.1/16"), []string{"test"}, ca, signer)
t1 := mustNebulaSSHToken(t, "test.lan", p.Name, p.ctl.Audiences.SSHRenew[0], now(), nil, crt, priv)
type args struct {
ctx context.Context
token string
}
tests := []struct {
name string
p *Nebula
args args
want *ssh.Certificate
wantErr bool
}{
{"fail", p, args{context.TODO(), t1}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.p.AuthorizeSSHRenew(tt.args.ctx, tt.args.token)
if (err != nil) != tt.wantErr {
t.Errorf("Nebula.AuthorizeSSHRenew() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Nebula.AuthorizeSSHRenew() = %v, want %v", got, tt.want)
}
})
}
}
func TestNebula_AuthorizeSSHRekey(t *testing.T) {
p, ca, signer := mustNebulaProvisioner(t)
crt, priv := mustNebulaCert(t, "test.lan", mustNebulaIPNet(t, "10.1.0.1/16"), []string{"test"}, ca, signer)
t1 := mustNebulaSSHToken(t, "test.lan", p.Name, p.ctl.Audiences.SSHRekey[0], now(), nil, crt, priv)
type args struct {
ctx context.Context
token string
}
tests := []struct {
name string
p *Nebula
args args
want *ssh.Certificate
want1 []SignOption
wantErr bool
}{
{"fail", p, args{context.TODO(), t1}, nil, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1, err := tt.p.AuthorizeSSHRekey(tt.args.ctx, tt.args.token)
if (err != nil) != tt.wantErr {
t.Errorf("Nebula.AuthorizeSSHRekey() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Nebula.AuthorizeSSHRekey() got = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got1, tt.want1) {
t.Errorf("Nebula.AuthorizeSSHRekey() got1 = %v, want %v", got1, tt.want1)
}
})
}
}
func TestNebula_authorizeToken(t *testing.T) {
t1 := now()
p, ca, signer := mustNebulaProvisioner(t)
crt, priv := mustNebulaCert(t, "test.lan", mustNebulaIPNet(t, "10.1.0.1/16"), []string{"test"}, ca, signer)
ok := mustNebulaToken(t, "test.lan", p.Name, p.ctl.Audiences.Sign[0], t1, []string{"10.1.0.1"}, crt, priv)
okNoSANs := mustNebulaToken(t, "test.lan", p.Name, p.ctl.Audiences.Sign[0], t1, nil, crt, priv)
okSSH := mustNebulaSSHToken(t, "test.lan", p.Name, p.ctl.Audiences.SSHSign[0], t1, &SignSSHOptions{
CertType: "host",
KeyID: "test.lan",
Principals: []string{"test.lan"},
}, crt, priv)
okSSHNoOptions := mustNebulaSSHToken(t, "test.lan", p.Name, p.ctl.Audiences.SSHSign[0], t1, nil, crt, priv)
// Token with errors
failNotBefore := mustNebulaToken(t, "test.lan", p.Name, p.ctl.Audiences.Sign[0], t1.Add(1*time.Hour), []string{"10.1.0.1"}, crt, priv)
failIssuer := mustNebulaToken(t, "test.lan", "foo", p.ctl.Audiences.Sign[0], t1, []string{"10.1.0.1"}, crt, priv)
failAudience := mustNebulaToken(t, "test.lan", p.Name, "foo", t1, []string{"10.1.0.1"}, crt, priv)
failSubject := mustNebulaToken(t, "", p.Name, p.ctl.Audiences.Sign[0], t1, []string{"10.1.0.1"}, crt, priv)
// Not a nebula token
jwk, err := generateJSONWebKey()
if err != nil {
t.Fatal(err)
}
simpleToken, err := generateSimpleToken("iss", "aud", jwk)
if err != nil {
t.Fatal(err)
}
// Provisioner with a different CA
p2, _, _ := mustNebulaProvisioner(t)
x509Claims := jose.Claims{
ID: "[REPLACEME]",
Subject: "test.lan",
Issuer: p.Name,
IssuedAt: jose.NewNumericDate(t1),
NotBefore: jose.NewNumericDate(t1),
Expiry: jose.NewNumericDate(t1.Add(5 * time.Minute)),
Audience: []string{p.ctl.Audiences.Sign[0]},
}
sshClaims := jose.Claims{
ID: "[REPLACEME]",
Subject: "test.lan",
Issuer: p.Name,
IssuedAt: jose.NewNumericDate(t1),
NotBefore: jose.NewNumericDate(t1),
Expiry: jose.NewNumericDate(t1.Add(5 * time.Minute)),
Audience: []string{p.ctl.Audiences.SSHSign[0]},
}
type args struct {
token string
audiences []string
}
tests := []struct {
name string
p *Nebula
args args
want *cert.NebulaCertificate
want1 *jwtPayload
wantErr bool
}{
{"ok x509", p, args{ok, p.ctl.Audiences.Sign}, crt, &jwtPayload{
Claims: x509Claims,
SANs: []string{"10.1.0.1"},
}, false},
{"ok x509 no sans", p, args{okNoSANs, p.ctl.Audiences.Sign}, crt, &jwtPayload{
Claims: x509Claims,
}, false},
{"ok ssh", p, args{okSSH, p.ctl.Audiences.SSHSign}, crt, &jwtPayload{
Claims: sshClaims,
Step: &stepPayload{
SSH: &SignSSHOptions{
CertType: "host",
KeyID: "test.lan",
Principals: []string{"test.lan"},
},
},
}, false},
{"ok ssh no principals", p, args{okSSHNoOptions, p.ctl.Audiences.SSHSign}, crt, &jwtPayload{
Claims: sshClaims,
}, false},
{"fail parse", p, args{"bad.token", p.ctl.Audiences.Sign}, nil, nil, true},
{"fail header", p, args{simpleToken, p.ctl.Audiences.Sign}, nil, nil, true},
{"fail verify", p2, args{ok, p.ctl.Audiences.Sign}, nil, nil, true},
{"fail claims nbf", p, args{failNotBefore, p.ctl.Audiences.Sign}, nil, nil, true},
{"fail claims iss", p, args{failIssuer, p.ctl.Audiences.Sign}, nil, nil, true},
{"fail claims aud", p, args{failAudience, p.ctl.Audiences.Sign}, nil, nil, true},
{"fail claims sub", p, args{failSubject, p.ctl.Audiences.Sign}, nil, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1, err := tt.p.authorizeToken(tt.args.token, tt.args.audiences)
if (err != nil) != tt.wantErr {
t.Errorf("Nebula.authorizeToken() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Nebula.authorizeToken() got = %#v, want %#v", got, tt.want)
t.Error(cmp.Equal(got, tt.want))
}
if got1 != nil && tt.want1 != nil {
tt.want1.ID = got1.ID
}
if !reflect.DeepEqual(got1, tt.want1) {
t.Errorf("Nebula.authorizeToken() got1 = %v, want %v", got1, tt.want1)
}
})
}
}
func Test_nebulaSANsValidator_Valid(t *testing.T) {
ipNet := mustNebulaIPNet(t, "10.1.2.3/16")
type fields struct {
Name string
IPs []*net.IPNet
}
type args struct {
req *x509.CertificateRequest
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{"ok", fields{"dns.name", []*net.IPNet{ipNet}}, args{&x509.CertificateRequest{
DNSNames: []string{"dns.name"},
IPAddresses: []net.IP{net.IPv4(10, 1, 2, 3)},
}}, false},
{"ok name only", fields{"dns.name", []*net.IPNet{ipNet}}, args{&x509.CertificateRequest{
DNSNames: []string{"dns.name"},
}}, false},
{"ok ip only", fields{"dns.name", []*net.IPNet{ipNet}}, args{&x509.CertificateRequest{
IPAddresses: []net.IP{net.IPv4(10, 1, 2, 3)},
}}, false},
{"ok email name", fields{"jane@doe.org", []*net.IPNet{ipNet}}, args{&x509.CertificateRequest{
EmailAddresses: []string{"jane@doe.org"},
IPAddresses: []net.IP{net.IPv4(10, 1, 2, 3)},
}}, false},
{"ok uri name", fields{"urn:foobar", []*net.IPNet{ipNet}}, args{&x509.CertificateRequest{
URIs: []*url.URL{{Scheme: "urn", Opaque: "foobar"}},
IPAddresses: []net.IP{net.IPv4(10, 1, 2, 3)},
}}, false},
{"ok ip name", fields{"127.0.0.1", []*net.IPNet{ipNet}}, args{&x509.CertificateRequest{
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv4(10, 1, 2, 3)},
}}, false},
{"ok multiple ips", fields{"dns.name", []*net.IPNet{ipNet, mustNebulaIPNet(t, "10.2.2.3/8")}}, args{&x509.CertificateRequest{
DNSNames: []string{"dns.name"},
IPAddresses: []net.IP{net.IPv4(10, 1, 2, 3), net.IPv4(10, 2, 2, 3)},
}}, false},
{"fail dns", fields{"fail.name", []*net.IPNet{ipNet}}, args{&x509.CertificateRequest{
DNSNames: []string{"dns.name"},
IPAddresses: []net.IP{net.IPv4(10, 1, 2, 3)},
}}, true},
{"fail email", fields{"fail@doe.org", []*net.IPNet{ipNet}}, args{&x509.CertificateRequest{
EmailAddresses: []string{"jane@doe.org"},
IPAddresses: []net.IP{net.IPv4(10, 1, 2, 3)},
}}, true},
{"fail uri", fields{"urn:barfoo", []*net.IPNet{ipNet}}, args{&x509.CertificateRequest{
URIs: []*url.URL{{Scheme: "urn", Opaque: "foobar"}},
IPAddresses: []net.IP{net.IPv4(10, 1, 2, 3)},
}}, true},
{"fail ip", fields{"127.0.0.1", []*net.IPNet{ipNet}}, args{&x509.CertificateRequest{
IPAddresses: []net.IP{net.IPv4(10, 1, 2, 1), net.IPv4(10, 1, 2, 3)},
}}, true},
{"fail nebula ip", fields{"dns.name", []*net.IPNet{ipNet}}, args{&x509.CertificateRequest{
DNSNames: []string{"dns.name"},
IPAddresses: []net.IP{net.IPv4(10, 2, 2, 3)},
}}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := nebulaSANsValidator{
Name: tt.fields.Name,
IPs: tt.fields.IPs,
}
if err := v.Valid(tt.args.req); (err != nil) != tt.wantErr {
t.Errorf("nebulaSANsValidator.Valid() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_nebulaPrincipalsValidator_Valid(t *testing.T) {
ipNet := mustNebulaIPNet(t, "10.1.2.3/16")
type fields struct {
Name string
IPs []*net.IPNet
}
type args struct {
got SignSSHOptions
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{"ok", fields{"dns.name", []*net.IPNet{ipNet}}, args{SignSSHOptions{
Principals: []string{"dns.name", "10.1.2.3"},
}}, false},
{"ok name", fields{"dns.name", []*net.IPNet{ipNet}}, args{SignSSHOptions{
Principals: []string{"dns.name"},
}}, false},
{"ok ip", fields{"dns.name", []*net.IPNet{ipNet}}, args{SignSSHOptions{
Principals: []string{"10.1.2.3"},
}}, false},
{"fail name", fields{"dns.name", []*net.IPNet{ipNet}}, args{SignSSHOptions{
Principals: []string{"foo.name", "10.1.2.3"},
}}, true},
{"fail ip", fields{"dns.name", []*net.IPNet{ipNet}}, args{SignSSHOptions{
Principals: []string{"dns.name", "10.2.2.3"},
}}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := nebulaPrincipalsValidator{
Name: tt.fields.Name,
IPs: tt.fields.IPs,
}
if err := v.Valid(tt.args.got); (err != nil) != tt.wantErr {
t.Errorf("nebulaPrincipalsValidator.Valid() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}