This commit is contained in:
max furman 2021-05-19 18:23:20 -07:00
parent 4f3e5ef64d
commit 638766c615
12 changed files with 123 additions and 6 deletions

View file

@ -13,6 +13,7 @@ import (
// provisioning flow.
type ACME struct {
*base
ID string `json:"-"`
Type string `json:"type"`
Name string `json:"name"`
ForceCN bool `json:"forceCN,omitempty"`
@ -23,6 +24,15 @@ type ACME struct {
// GetID returns the provisioner unique identifier.
func (p ACME) GetID() string {
if p.ID != "" {
return p.ID
}
return p.GetIDForToken()
}
// GetIDForToken returns an identifier that will be used to load the provisioner
// from a token.
func (p *ACME) GetIDForToken() string {
return "acme/" + p.Name
}

View file

@ -252,6 +252,7 @@ type awsInstanceIdentityDocument struct {
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
type AWS struct {
*base
ID string `json:"-"`
Type string `json:"type"`
Name string `json:"name"`
Accounts []string `json:"accounts"`
@ -269,6 +270,15 @@ type AWS struct {
// GetID returns the provisioner unique identifier.
func (p *AWS) GetID() string {
if p.ID != "" {
return p.ID
}
return p.GetIDForToken()
}
// GetIDForToken returns an identifier that will be used to load the provisioner
// from a token.
func (p *AWS) GetIDForToken() string {
return "aws/" + p.Name
}

View file

@ -84,6 +84,7 @@ type azurePayload struct {
// and https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service
type Azure struct {
*base
ID string `json:"-"`
Type string `json:"type"`
Name string `json:"name"`
TenantID string `json:"tenantID"`
@ -101,6 +102,15 @@ type Azure struct {
// GetID returns the provisioner unique identifier.
func (p *Azure) GetID() string {
if p.ID != "" {
return p.ID
}
return p.GetIDForToken()
}
// GetIDForToken returns an identifier that will be used to load the provisioner
// from a token.
func (p *Azure) GetIDForToken() string {
return p.TenantID
}

View file

@ -46,6 +46,7 @@ type Collection struct {
byID *sync.Map
byKey *sync.Map
byName *sync.Map
byTokenID *sync.Map
sorted provisionerSlice
audiences Audiences
}
@ -57,6 +58,7 @@ func NewCollection(audiences Audiences) *Collection {
byID: new(sync.Map),
byKey: new(sync.Map),
byName: new(sync.Map),
byTokenID: new(sync.Map),
audiences: audiences,
}
}
@ -71,6 +73,13 @@ func (c *Collection) LoadByName(name string) (Interface, bool) {
return loadProvisioner(c.byName, name)
}
// LoadByTokenID a provisioner by identifier found in token.
// For different provisioner types this identifier may be found in in different
// attributes of the token.
func (c *Collection) LoadByTokenID(tokenProvisionerID string) (Interface, bool) {
return loadProvisioner(c.byTokenID, tokenProvisionerID)
}
// LoadByToken parses the token claims and loads the provisioner associated.
func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims) (Interface, bool) {
var audiences []string
@ -86,11 +95,12 @@ func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims)
if matchesAudience(claims.Audience, audiences) {
// Use fragment to get provisioner name (GCP, AWS, SSHPOP)
if fragment != "" {
return c.Load(fragment)
return c.LoadByTokenID(fragment)
}
// If matches with stored audiences it will be a JWT token (default), and
// the id would be <issuer>:<kid>.
return c.Load(claims.Issuer + ":" + token.Headers[0].KeyID)
// TODO: is this ok?
return c.LoadByTokenID(claims.Issuer + ":" + token.Headers[0].KeyID)
}
// The ID will be just the clientID stored in azp, aud or tid.
@ -101,7 +111,7 @@ func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims)
// Kubernetes Service Account tokens.
if payload.Issuer == k8sSAIssuer {
if p, ok := c.Load(K8sSAID); ok {
if p, ok := c.LoadByTokenID(K8sSAID); ok {
return p, ok
}
// Kubernetes service account provisioner not found
@ -115,18 +125,18 @@ func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims)
// Try with azp (OIDC)
if len(payload.AuthorizedParty) > 0 {
if p, ok := c.Load(payload.AuthorizedParty); ok {
if p, ok := c.LoadByTokenID(payload.AuthorizedParty); ok {
return p, ok
}
}
// Try with tid (Azure)
if payload.TenantID != "" {
if p, ok := c.Load(payload.TenantID); ok {
if p, ok := c.LoadByTokenID(payload.TenantID); ok {
return p, ok
}
}
// Fallback to aud
return c.Load(payload.Audience[0])
return c.LoadByTokenID(payload.Audience[0])
}
// LoadByCertificate looks for the provisioner extension and extracts the
@ -185,6 +195,12 @@ func (c *Collection) Store(p Interface) error {
c.byID.Delete(p.GetID())
return errors.New("cannot add multiple provisioners with the same name")
}
// Store provisioner always by ID presented in token.
if _, loaded := c.byTokenID.LoadOrStore(p.GetIDForToken(), p); loaded {
c.byID.Delete(p.GetID())
c.byName.Delete(p.GetName())
return errors.New("cannot add multiple provisioners with the same token identifier")
}
// Store provisioner in byKey if EncryptedKey is defined.
if kid, _, ok := p.GetEncryptedKey(); ok {

View file

@ -78,6 +78,7 @@ func newGCPConfig() *gcpConfig {
// https://cloud.google.com/compute/docs/instances/verifying-instance-identity
type GCP struct {
*base
ID string `json:"-"`
Type string `json:"type"`
Name string `json:"name"`
ServiceAccounts []string `json:"serviceAccounts"`
@ -96,6 +97,16 @@ type GCP struct {
// GetID returns the provisioner unique identifier. The name should uniquely
// identify any GCP provisioner.
func (p *GCP) GetID() string {
if p.ID != "" {
return p.ID
}
return p.GetIDForToken()
}
// GetIDForToken returns an identifier that will be used to load the provisioner
// from a token.
func (p *GCP) GetIDForToken() string {
return "gcp/" + p.Name
}

View file

@ -45,6 +45,12 @@ func (p *JWK) GetID() string {
if p.ID != "" {
return p.ID
}
return p.GetIDForToken()
}
// GetIDForToken returns an identifier that will be used to load the provisioner
// from a token.
func (p *JWK) GetIDForToken() string {
return p.Name + ":" + p.Key.KeyID
}

View file

@ -42,6 +42,7 @@ type k8sSAPayload struct {
// entity trusted to make signature requests.
type K8sSA struct {
*base
ID string `json:"-"`
Type string `json:"type"`
Name string `json:"name"`
PubKeys []byte `json:"publicKeys,omitempty"`
@ -56,6 +57,15 @@ type K8sSA struct {
// GetID returns the provisioner unique identifier. The name and credential id
// should uniquely identify any K8sSA provisioner.
func (p *K8sSA) GetID() string {
if p.ID != "" {
return p.ID
}
return p.GetIDForToken()
}
// GetIDForToken returns an identifier that will be used to load the provisioner
// from a token.
func (p *K8sSA) GetIDForToken() string {
return K8sSAID
}

View file

@ -14,6 +14,10 @@ func (p *noop) GetID() string {
return "noop"
}
func (p *noop) GetIDForToken() string {
return "noop"
}
func (p *noop) GetTokenID(token string) (string, error) {
return "", nil
}

View file

@ -54,6 +54,7 @@ type openIDPayload struct {
// ClientSecret is mandatory, but it can be an empty string.
type OIDC struct {
*base
ID string `json:"-"`
Type string `json:"type"`
Name string `json:"name"`
ClientID string `json:"clientID"`
@ -111,6 +112,15 @@ func sanitizeEmail(email string) string {
// GetID returns the provisioner unique identifier, the OIDC provisioner the
// uses the clientID for this.
func (o *OIDC) GetID() string {
if o.ID != "" {
return o.ID
}
return o.GetIDForToken()
}
// GetIDForToken returns an identifier that will be used to load the provisioner
// from a token.
func (o *OIDC) GetIDForToken() string {
return o.ClientID
}

View file

@ -17,6 +17,7 @@ import (
// Interface is the interface that all provisioner types must implement.
type Interface interface {
GetID() string
GetIDForToken() string
GetTokenID(token string) (string, error)
GetName() string
GetType() Type
@ -388,6 +389,7 @@ type MockProvisioner struct {
Mret1, Mret2, Mret3 interface{}
Merr error
MgetID func() string
MgetIDForToken func() string
MgetTokenID func(string) (string, error)
MgetName func() string
MgetType func() Type
@ -410,6 +412,14 @@ func (m *MockProvisioner) GetID() string {
return m.Mret1.(string)
}
// GetIDForToken mock
func (m *MockProvisioner) GetIDForToken() string {
if m.MgetIDForToken != nil {
return m.MgetIDForToken()
}
return m.Mret1.(string)
}
// GetTokenID mock
func (m *MockProvisioner) GetTokenID(token string) (string, error) {
if m.MgetTokenID != nil {

View file

@ -26,6 +26,7 @@ type sshPOPPayload struct {
// signature requests.
type SSHPOP struct {
*base
ID string `json:"-"`
Type string `json:"type"`
Name string `json:"name"`
Claims *Claims `json:"claims,omitempty"`
@ -38,6 +39,15 @@ type SSHPOP struct {
// GetID returns the provisioner unique identifier. The name and credential id
// should uniquely identify any SSH-POP provisioner.
func (p *SSHPOP) GetID() string {
if p.ID != "" {
return p.ID
}
return p.GetIDForToken()
}
// GetIDForToken returns an identifier that will be used to load the provisioner
// from a token.
func (p *SSHPOP) GetIDForToken() string {
return "sshpop/" + p.Name
}

View file

@ -26,6 +26,7 @@ type x5cPayload struct {
// signature requests.
type X5C struct {
*base
ID string `json:"-"`
Type string `json:"type"`
Name string `json:"name"`
Roots []byte `json:"roots"`
@ -39,6 +40,15 @@ type X5C struct {
// GetID returns the provisioner unique identifier. The name and credential id
// should uniquely identify any X5C provisioner.
func (p *X5C) GetID() string {
if p.ID != "" {
return p.ID
}
return p.GetIDForToken()
}
// GetIDForToken returns an identifier that will be used to load the provisioner
// from a token.
func (p *X5C) GetIDForToken() string {
return "x5c/" + p.Name
}