forked from TrueCloudLab/certificates
Address review comments.
This commit is contained in:
parent
b424aa3dc1
commit
449a9fdfd6
1 changed files with 30 additions and 22 deletions
|
@ -9,7 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/slackhq/nebula/cert"
|
nebula "github.com/slackhq/nebula/cert"
|
||||||
"github.com/smallstep/certificates/errs"
|
"github.com/smallstep/certificates/errs"
|
||||||
"go.step.sm/crypto/jose"
|
"go.step.sm/crypto/jose"
|
||||||
"go.step.sm/crypto/sshutil"
|
"go.step.sm/crypto/sshutil"
|
||||||
|
@ -19,16 +19,17 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// NebulaCertHeader is the token header that contains a nebula certificate.
|
// NebulaCertHeader is the token header that contains a Nebula certificate.
|
||||||
NebulaCertHeader jose.HeaderKey = "nebula"
|
NebulaCertHeader jose.HeaderKey = "nebula"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Nebula is a provisioner that verifies tokens signed using nebula private
|
// Nebula is a provisioner that verifies tokens signed using Nebula private
|
||||||
// keys. The tokens embed a header parameter with the certificate that can be
|
// keys. The tokens contain a Nebula certificate in the header, which can be
|
||||||
// used to verify the signature. Those certificates are verified using the
|
// used to verify the token signature. The certificates are themselves verified
|
||||||
// Nebula CAs encoded in Roots. The process is similar to X5C or SSHPOP tokens.
|
// using the Nebula CA certificates encoded in Roots. The verification process
|
||||||
|
// is similar to the process for X5C tokens.
|
||||||
//
|
//
|
||||||
// Because of Nebula "leaf" certificates use X25519 keys, the tokens are signed
|
// Because Nebula "leaf" certificates use X25519 keys, the tokens are signed
|
||||||
// using XEd25519 defined at
|
// using XEd25519 defined at
|
||||||
// https://signal.org/docs/specifications/xeddsa/#xeddsa and implemented by
|
// https://signal.org/docs/specifications/xeddsa/#xeddsa and implemented by
|
||||||
// go.step.sm/crypto/x25519.
|
// go.step.sm/crypto/x25519.
|
||||||
|
@ -40,11 +41,11 @@ type Nebula struct {
|
||||||
Claims *Claims `json:"claims,omitempty"`
|
Claims *Claims `json:"claims,omitempty"`
|
||||||
Options *Options `json:"options,omitempty"`
|
Options *Options `json:"options,omitempty"`
|
||||||
claimer *Claimer
|
claimer *Claimer
|
||||||
caPool *cert.NebulaCAPool
|
caPool *nebula.NebulaCAPool
|
||||||
audiences Audiences
|
audiences Audiences
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init verifies and initializes the nebula provisioner.
|
// Init verifies and initializes the Nebula provisioner.
|
||||||
func (p *Nebula) Init(config Config) error {
|
func (p *Nebula) Init(config Config) error {
|
||||||
switch {
|
switch {
|
||||||
case p.Type == "":
|
case p.Type == "":
|
||||||
|
@ -60,7 +61,7 @@ func (p *Nebula) Init(config Config) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
p.caPool, err = cert.NewCAPoolFromBytes(p.Roots)
|
p.caPool, err = nebula.NewCAPoolFromBytes(p.Roots)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errs.InternalServer("failed to create ca pool: %v", err)
|
return errs.InternalServer("failed to create ca pool: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -138,7 +139,7 @@ func (p *Nebula) AuthorizeSign(ctx context.Context, token string) ([]SignOption,
|
||||||
data.SetToken(v)
|
data.SetToken(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The nebula certificate will be available using the template variable Crt.
|
// The Nebula certificate will be available using the template variable Crt.
|
||||||
// For example {{ .Crt.Details.Groups }} can be used to get all the groups.
|
// For example {{ .Crt.Details.Groups }} can be used to get all the groups.
|
||||||
data.SetAuthorizationCertificate(crt)
|
data.SetAuthorizationCertificate(crt)
|
||||||
|
|
||||||
|
@ -168,7 +169,7 @@ func (p *Nebula) AuthorizeSign(ctx context.Context, token string) ([]SignOption,
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
|
// AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
|
||||||
// Currently the nebula provisioner only grant host ssh certificates
|
// Currently the Nebula provisioner only grants host SSH certificates.
|
||||||
func (p *Nebula) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
|
func (p *Nebula) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
|
||||||
if !p.claimer.IsSSHCAEnabled() {
|
if !p.claimer.IsSSHCAEnabled() {
|
||||||
return nil, errs.Unauthorized("ssh is disabled for nebula provisioner '%s'", p.Name)
|
return nil, errs.Unauthorized("ssh is disabled for nebula provisioner '%s'", p.Name)
|
||||||
|
@ -240,7 +241,7 @@ func (p *Nebula) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOpti
|
||||||
data.SetToken(v)
|
data.SetToken(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The nebula certificate will be available using the template variable Crt.
|
// The Nebula certificate will be available using the template variable Crt.
|
||||||
// For example {{ .AuthorizationCrt.Details.Groups }} can be used to get all the groups.
|
// For example {{ .AuthorizationCrt.Details.Groups }} can be used to get all the groups.
|
||||||
data.SetAuthorizationCertificate(crt)
|
data.SetAuthorizationCertificate(crt)
|
||||||
|
|
||||||
|
@ -301,13 +302,13 @@ func (p *Nebula) validateToken(token string, audiences []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Nebula) authorizeToken(token string, audiences []string) (*cert.NebulaCertificate, *jwtPayload, error) {
|
func (p *Nebula) authorizeToken(token string, audiences []string) (*nebula.NebulaCertificate, *jwtPayload, error) {
|
||||||
jwt, err := jose.ParseSigned(token)
|
jwt, err := jose.ParseSigned(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errs.UnauthorizedErr(err, errs.WithMessage("failed to parse token"))
|
return nil, nil, errs.UnauthorizedErr(err, errs.WithMessage("failed to parse token"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract nebula certificate
|
// Extract Nebula certificate
|
||||||
h, ok := jwt.Headers[0].ExtraHeaders[NebulaCertHeader]
|
h, ok := jwt.Headers[0].ExtraHeaders[NebulaCertHeader]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, errs.Unauthorized("failed to parse token: nebula header is missing")
|
return nil, nil, errs.Unauthorized("failed to parse token: nebula header is missing")
|
||||||
|
@ -320,7 +321,7 @@ func (p *Nebula) authorizeToken(token string, audiences []string) (*cert.NebulaC
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errs.UnauthorizedErr(err, errs.WithMessage("failed to parse token: nebula header is not valid"))
|
return nil, nil, errs.UnauthorizedErr(err, errs.WithMessage("failed to parse token: nebula header is not valid"))
|
||||||
}
|
}
|
||||||
c, err := cert.UnmarshalNebulaCertificate(b)
|
c, err := nebula.UnmarshalNebulaCertificate(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errs.UnauthorizedErr(err, errs.WithMessage("failed to parse nebula certificate: nebula header is not valid"))
|
return nil, nil, errs.UnauthorizedErr(err, errs.WithMessage("failed to parse nebula certificate: nebula header is not valid"))
|
||||||
}
|
}
|
||||||
|
@ -401,15 +402,18 @@ func (v nebulaSANsValidator) Valid(req *x509.CertificateRequest) error {
|
||||||
}
|
}
|
||||||
// Check ip network
|
// Check ip network
|
||||||
if !valid {
|
if !valid {
|
||||||
for _, ipnet := range v.IPs {
|
for _, ipNet := range v.IPs {
|
||||||
if ip.Equal(ipnet.IP) {
|
if ip.Equal(ipNet.IP) {
|
||||||
valid = true
|
valid = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !valid {
|
if !valid {
|
||||||
return errs.Forbidden("certificate request does not contain a valid IP addresses - got %v, want %v", req.IPAddresses, v.IPs)
|
for _, ipNet := range v.IPs {
|
||||||
|
ips = append(ips, ipNet.IP)
|
||||||
|
}
|
||||||
|
return errs.Forbidden("certificate request contains invalid IP addresses - got %v, want %v", req.IPAddresses, ips)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -423,7 +427,7 @@ type nebulaPrincipalsValidator struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Valid checks that the SignSSHOptions principals contains only names in the
|
// Valid checks that the SignSSHOptions principals contains only names in the
|
||||||
// nebula certificate.
|
// Nebula certificate.
|
||||||
func (v nebulaPrincipalsValidator) Valid(got SignSSHOptions) error {
|
func (v nebulaPrincipalsValidator) Valid(got SignSSHOptions) error {
|
||||||
for _, p := range got.Principals {
|
for _, p := range got.Principals {
|
||||||
var valid bool
|
var valid bool
|
||||||
|
@ -442,9 +446,13 @@ func (v nebulaPrincipalsValidator) Valid(got SignSSHOptions) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !valid {
|
if !valid {
|
||||||
|
ips := make([]net.IP, len(v.IPs))
|
||||||
|
for i, ipNet := range v.IPs {
|
||||||
|
ips[i] = ipNet.IP
|
||||||
|
}
|
||||||
return errs.Forbidden(
|
return errs.Forbidden(
|
||||||
"ssh certificate principals does not contain a valid name or IP address - got %v, want %s or %v",
|
"ssh certificate principals contains invalid name or IP addresses - got %v, want %s or %v",
|
||||||
got.Principals, v.Name, v.IPs,
|
got.Principals, v.Name, ips,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue