forked from TrueCloudLab/certificates
Refactor admin token to use with RAs.
This commit is contained in:
parent
db337debcd
commit
c55b27a2fc
9 changed files with 114 additions and 50 deletions
|
@ -130,22 +130,19 @@ func (a *Authority) AuthorizeAdminToken(r *http.Request, token string) (*linkedc
|
|||
// According to "rfc7519 JSON Web Token" acceptable skew should be no
|
||||
// more than a few minutes.
|
||||
if err := claims.ValidateWithLeeway(jose.Expected{
|
||||
Issuer: prov.GetName(),
|
||||
Issuer: "step-admin-client/1.0",
|
||||
Time: time.Now().UTC(),
|
||||
}, time.Minute); err != nil {
|
||||
return nil, admin.WrapError(admin.ErrorUnauthorizedType, err, "x5c.authorizeToken; invalid x5c claims")
|
||||
}
|
||||
|
||||
// validate audience: path matches the current path
|
||||
if r.URL.Path != claims.Audience[0] {
|
||||
return nil, admin.NewError(admin.ErrorUnauthorizedType,
|
||||
"x5c.authorizeToken; x5c token has invalid audience "+
|
||||
"claim (aud); expected %s, but got %s", r.URL.Path, claims.Audience)
|
||||
if !matchesAudience(claims.Audience, a.config.Audience(r.URL.Path)) {
|
||||
return nil, admin.NewError(admin.ErrorUnauthorizedType, "x5c.authorizeToken; x5c token has invalid audience claim (aud)")
|
||||
}
|
||||
|
||||
if claims.Subject == "" {
|
||||
return nil, admin.NewError(admin.ErrorUnauthorizedType,
|
||||
"x5c.authorizeToken; x5c token subject cannot be empty")
|
||||
return nil, admin.NewError(admin.ErrorUnauthorizedType, "x5c.authorizeToken; x5c token subject cannot be empty")
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -156,7 +153,7 @@ func (a *Authority) AuthorizeAdminToken(r *http.Request, token string) (*linkedc
|
|||
adminSANs := append([]string{leaf.Subject.CommonName}, leaf.DNSNames...)
|
||||
adminSANs = append(adminSANs, leaf.EmailAddresses...)
|
||||
for _, san := range adminSANs {
|
||||
if adm, ok = a.LoadAdminBySubProv(san, claims.Issuer); ok {
|
||||
if adm, ok = a.LoadAdminBySubProv(san, prov.GetName()); ok {
|
||||
adminFound = true
|
||||
break
|
||||
}
|
||||
|
|
|
@ -304,6 +304,18 @@ func (c *Config) GetAudiences() provisioner.Audiences {
|
|||
return audiences
|
||||
}
|
||||
|
||||
// Audience returns the list of audiences for a given path.
|
||||
func (c *Config) Audience(path string) []string {
|
||||
audiences := make([]string, len(c.DNSNames)+1)
|
||||
for i, name := range c.DNSNames {
|
||||
hostname := toHostname(name)
|
||||
audiences[i] = "https://" + hostname + path
|
||||
}
|
||||
// For backward compatibility
|
||||
audiences[len(c.DNSNames)] = path
|
||||
return audiences
|
||||
}
|
||||
|
||||
func toHostname(name string) string {
|
||||
// ensure an IPv6 address is represented with square brackets when used as hostname
|
||||
if ip := net.ParseIP(name); ip != nil && ip.To4() == nil {
|
||||
|
|
|
@ -2,6 +2,7 @@ package config
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
@ -317,3 +318,38 @@ func Test_toHostname(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig_Audience(t *testing.T) {
|
||||
type fields struct {
|
||||
DNSNames []string
|
||||
}
|
||||
type args struct {
|
||||
path string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want []string
|
||||
}{
|
||||
{"ok", fields{[]string{
|
||||
"ca", "ca.example.com", "127.0.0.1", "::1",
|
||||
}}, args{"/path"}, []string{
|
||||
"https://ca/path",
|
||||
"https://ca.example.com/path",
|
||||
"https://127.0.0.1/path",
|
||||
"https://[::1]/path",
|
||||
"/path",
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Config{
|
||||
DNSNames: tt.fields.DNSNames,
|
||||
}
|
||||
if got := c.Audience(tt.args.path); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Config.Audience() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -235,6 +235,28 @@ func (c *linkedCaClient) DeleteAdmin(ctx context.Context, id string) error {
|
|||
return errors.Wrap(err, "error deleting admin")
|
||||
}
|
||||
|
||||
func (c *linkedCaClient) GetCertificateData(serial string) (*db.CertificateData, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
resp, err := c.client.GetCertificate(ctx, &linkedca.GetCertificateRequest{
|
||||
Serial: serial,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var provisioner *db.ProvisionerData
|
||||
if p := resp.Provisioner; p != nil {
|
||||
provisioner = &db.ProvisionerData{
|
||||
ID: p.Id, Name: p.Name, Type: p.Type.String(),
|
||||
}
|
||||
}
|
||||
return &db.CertificateData{
|
||||
Provisioner: provisioner,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *linkedCaClient) StoreCertificateChain(prov provisioner.Interface, fullchain ...*x509.Certificate) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
|
|
@ -54,14 +54,19 @@ func (a *Authority) LoadProvisionerByCertificate(crt *x509.Certificate) (provisi
|
|||
return p, nil
|
||||
}
|
||||
|
||||
// Attempt to load the provisioner using the linked db
|
||||
// TODO:(mariano)
|
||||
|
||||
// Attempt to load the provisioner from the db
|
||||
if db, ok := a.db.(interface {
|
||||
// certificateDataGetter is an interface that can be use to retrieve the
|
||||
// provisioner from a db or a linked ca.
|
||||
type certificateDataGetter interface {
|
||||
GetCertificateData(string) (*db.CertificateData, error)
|
||||
}); ok {
|
||||
if data, err := db.GetCertificateData(crt.SerialNumber.String()); err == nil && data.Provisioner != nil {
|
||||
}
|
||||
var cdg certificateDataGetter
|
||||
if getter, ok := a.adminDB.(certificateDataGetter); ok {
|
||||
cdg = getter
|
||||
} else if getter, ok := a.db.(certificateDataGetter); ok {
|
||||
cdg = getter
|
||||
}
|
||||
if cdg != nil {
|
||||
if data, err := cdg.GetCertificateData(crt.SerialNumber.String()); err == nil && data.Provisioner != nil {
|
||||
loadProvisioner = func() (provisioner.Interface, error) {
|
||||
p, ok := a.provisioners.Load(data.Provisioner.ID)
|
||||
if !ok {
|
||||
|
|
|
@ -23,7 +23,10 @@ import (
|
|||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
var adminURLPrefix = "admin"
|
||||
const (
|
||||
adminURLPrefix = "admin"
|
||||
adminIssuer = "step-admin-client/1.0"
|
||||
)
|
||||
|
||||
// AdminClient implements an HTTP client for the CA server.
|
||||
type AdminClient struct {
|
||||
|
@ -35,7 +38,6 @@ type AdminClient struct {
|
|||
x5cCertFile string
|
||||
x5cCertStrs []string
|
||||
x5cCert *x509.Certificate
|
||||
x5cIssuer string
|
||||
x5cSubject string
|
||||
}
|
||||
|
||||
|
@ -77,12 +79,11 @@ func NewAdminClient(endpoint string, opts ...ClientOption) (*AdminClient, error)
|
|||
x5cCertFile: o.x5cCertFile,
|
||||
x5cCertStrs: o.x5cCertStrs,
|
||||
x5cCert: o.x5cCert,
|
||||
x5cIssuer: o.x5cIssuer,
|
||||
x5cSubject: o.x5cSubject,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *AdminClient) generateAdminToken(urlPath string) (string, error) {
|
||||
func (c *AdminClient) generateAdminToken(aud *url.URL) (string, error) {
|
||||
// A random jwt id will be used to identify duplicated tokens
|
||||
jwtID, err := randutil.Hex(64) // 256 bits
|
||||
if err != nil {
|
||||
|
@ -93,8 +94,8 @@ func (c *AdminClient) generateAdminToken(urlPath string) (string, error) {
|
|||
tokOptions := []token.Options{
|
||||
token.WithJWTID(jwtID),
|
||||
token.WithKid(c.x5cJWK.KeyID),
|
||||
token.WithIssuer(c.x5cIssuer),
|
||||
token.WithAudience(urlPath),
|
||||
token.WithIssuer(adminIssuer),
|
||||
token.WithAudience(aud.String()),
|
||||
token.WithValidity(now, now.Add(token.DefaultValidity)),
|
||||
token.WithX5CCerts(c.x5cCertStrs),
|
||||
}
|
||||
|
@ -205,7 +206,7 @@ func (c *AdminClient) GetAdminsPaginate(opts ...AdminOption) (*adminAPI.GetAdmin
|
|||
Path: "/admin/admins",
|
||||
RawQuery: o.rawQuery(),
|
||||
})
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
tok, err := c.generateAdminToken(u)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
|
@ -260,7 +261,7 @@ func (c *AdminClient) CreateAdmin(createAdminRequest *adminAPI.CreateAdminReques
|
|||
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
||||
}
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: "/admin/admins"})
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
tok, err := c.generateAdminToken(u)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
|
@ -292,7 +293,7 @@ retry:
|
|||
func (c *AdminClient) RemoveAdmin(id string) error {
|
||||
var retried bool
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "admins", id)})
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
tok, err := c.generateAdminToken(u)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
|
@ -324,7 +325,7 @@ func (c *AdminClient) UpdateAdmin(id string, uar *adminAPI.UpdateAdminRequest) (
|
|||
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
||||
}
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "admins", id)})
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
tok, err := c.generateAdminToken(u)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
|
@ -371,7 +372,7 @@ func (c *AdminClient) GetProvisioner(opts ...ProvisionerOption) (*linkedca.Provi
|
|||
default:
|
||||
return nil, errors.New("must set either name or id in method options")
|
||||
}
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
tok, err := c.generateAdminToken(u)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
|
@ -410,7 +411,7 @@ func (c *AdminClient) GetProvisionersPaginate(opts ...ProvisionerOption) (*admin
|
|||
Path: "/admin/provisioners",
|
||||
RawQuery: o.rawQuery(),
|
||||
})
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
tok, err := c.generateAdminToken(u)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
|
@ -480,7 +481,7 @@ func (c *AdminClient) RemoveProvisioner(opts ...ProvisionerOption) error {
|
|||
default:
|
||||
return errors.New("must set either name or id in method options")
|
||||
}
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
tok, err := c.generateAdminToken(u)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
|
@ -512,7 +513,7 @@ func (c *AdminClient) CreateProvisioner(prov *linkedca.Provisioner) (*linkedca.P
|
|||
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
||||
}
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners")})
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
tok, err := c.generateAdminToken(u)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
|
@ -548,7 +549,7 @@ func (c *AdminClient) UpdateProvisioner(name string, prov *linkedca.Provisioner)
|
|||
return errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
||||
}
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", name)})
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
tok, err := c.generateAdminToken(u)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
|
@ -587,7 +588,7 @@ func (c *AdminClient) GetExternalAccountKeysPaginate(provisionerName, reference
|
|||
Path: p,
|
||||
RawQuery: o.rawQuery(),
|
||||
})
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
tok, err := c.generateAdminToken(u)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
|
@ -623,7 +624,7 @@ func (c *AdminClient) CreateExternalAccountKey(provisionerName string, eakReques
|
|||
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
||||
}
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "acme/eab/", provisionerName)})
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
tok, err := c.generateAdminToken(u)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
|
@ -655,7 +656,7 @@ retry:
|
|||
func (c *AdminClient) RemoveExternalAccountKey(provisionerName, keyID string) error {
|
||||
var retried bool
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "acme/eab", provisionerName, "/", keyID)})
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
tok, err := c.generateAdminToken(u)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
|
|
21
ca/client.go
21
ca/client.go
|
@ -116,7 +116,6 @@ type clientOptions struct {
|
|||
x5cCertFile string
|
||||
x5cCertStrs []string
|
||||
x5cCert *x509.Certificate
|
||||
x5cIssuer string
|
||||
x5cSubject string
|
||||
}
|
||||
|
||||
|
@ -332,19 +331,13 @@ func WithAdminX5C(certs []*x509.Certificate, key interface{}, passwordFile strin
|
|||
}
|
||||
|
||||
o.x5cCert = certs[0]
|
||||
o.x5cSubject = o.x5cCert.Subject.CommonName
|
||||
|
||||
for _, e := range o.x5cCert.Extensions {
|
||||
if e.Id.Equal(stepOIDProvisioner) {
|
||||
var prov stepProvisionerASN1
|
||||
if _, err := asn1.Unmarshal(e.Value, &prov); err != nil {
|
||||
return errors.Wrap(err, "error unmarshaling provisioner OID from certificate")
|
||||
}
|
||||
o.x5cIssuer = string(prov.Name)
|
||||
}
|
||||
}
|
||||
if o.x5cIssuer == "" {
|
||||
return errors.New("provisioner extension not found in certificate")
|
||||
switch leaf := certs[0]; {
|
||||
case leaf.Subject.CommonName != "":
|
||||
o.x5cSubject = leaf.Subject.CommonName
|
||||
case len(leaf.DNSNames) > 0:
|
||||
o.x5cSubject = leaf.DNSNames[0]
|
||||
case len(leaf.EmailAddresses) > 0:
|
||||
o.x5cSubject = leaf.EmailAddresses[0]
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
2
go.mod
2
go.mod
|
@ -50,4 +50,4 @@ require (
|
|||
// replace github.com/smallstep/nosql => ../nosql
|
||||
// replace go.step.sm/crypto => ../crypto
|
||||
// replace go.step.sm/cli-utils => ../cli-utils
|
||||
// replace go.step.sm/linkedca => ../linkedca
|
||||
replace go.step.sm/linkedca => ../linkedca
|
||||
|
|
2
go.sum
2
go.sum
|
@ -709,8 +709,6 @@ go.step.sm/cli-utils v0.7.0/go.mod h1:Ur6bqA/yl636kCUJbp30J7Unv5JJ226eW2KqXPDwF/
|
|||
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
|
||||
go.step.sm/crypto v0.15.3 h1:f3GMl+aCydt294BZRjTYwpaXRqwwndvoTY2NLN4wu10=
|
||||
go.step.sm/crypto v0.15.3/go.mod h1:3G0yQr5lQqfEG0CMYz8apC/qMtjLRQlzflL2AxkcN+g=
|
||||
go.step.sm/linkedca v0.12.0 h1:FA18uJO5P6W2pklcezMs+w+N3dVbpKEE1LP9HLsJgg4=
|
||||
go.step.sm/linkedca v0.12.0/go.mod h1:W59ucS4vFpuR0g4PtkGbbtXAwxbDEnNCg+ovkej1ANM=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
|
|
Loading…
Reference in a new issue