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. // provisioning flow.
type ACME struct { type ACME struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
ForceCN bool `json:"forceCN,omitempty"` ForceCN bool `json:"forceCN,omitempty"`
@ -23,6 +24,15 @@ type ACME struct {
// GetID returns the provisioner unique identifier. // GetID returns the provisioner unique identifier.
func (p ACME) GetID() string { 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 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 // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
type AWS struct { type AWS struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Accounts []string `json:"accounts"` Accounts []string `json:"accounts"`
@ -269,6 +270,15 @@ type AWS struct {
// GetID returns the provisioner unique identifier. // GetID returns the provisioner unique identifier.
func (p *AWS) GetID() string { 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 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 // and https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service
type Azure struct { type Azure struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
TenantID string `json:"tenantID"` TenantID string `json:"tenantID"`
@ -101,6 +102,15 @@ type Azure struct {
// GetID returns the provisioner unique identifier. // GetID returns the provisioner unique identifier.
func (p *Azure) GetID() string { 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 return p.TenantID
} }

View file

@ -46,6 +46,7 @@ type Collection struct {
byID *sync.Map byID *sync.Map
byKey *sync.Map byKey *sync.Map
byName *sync.Map byName *sync.Map
byTokenID *sync.Map
sorted provisionerSlice sorted provisionerSlice
audiences Audiences audiences Audiences
} }
@ -57,6 +58,7 @@ func NewCollection(audiences Audiences) *Collection {
byID: new(sync.Map), byID: new(sync.Map),
byKey: new(sync.Map), byKey: new(sync.Map),
byName: new(sync.Map), byName: new(sync.Map),
byTokenID: new(sync.Map),
audiences: audiences, audiences: audiences,
} }
} }
@ -71,6 +73,13 @@ func (c *Collection) LoadByName(name string) (Interface, bool) {
return loadProvisioner(c.byName, name) 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. // LoadByToken parses the token claims and loads the provisioner associated.
func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims) (Interface, bool) { func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims) (Interface, bool) {
var audiences []string var audiences []string
@ -86,11 +95,12 @@ func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims)
if matchesAudience(claims.Audience, audiences) { if matchesAudience(claims.Audience, audiences) {
// Use fragment to get provisioner name (GCP, AWS, SSHPOP) // Use fragment to get provisioner name (GCP, AWS, SSHPOP)
if fragment != "" { if fragment != "" {
return c.Load(fragment) return c.LoadByTokenID(fragment)
} }
// If matches with stored audiences it will be a JWT token (default), and // If matches with stored audiences it will be a JWT token (default), and
// the id would be <issuer>:<kid>. // 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. // 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. // Kubernetes Service Account tokens.
if payload.Issuer == k8sSAIssuer { if payload.Issuer == k8sSAIssuer {
if p, ok := c.Load(K8sSAID); ok { if p, ok := c.LoadByTokenID(K8sSAID); ok {
return p, ok return p, ok
} }
// Kubernetes service account provisioner not found // Kubernetes service account provisioner not found
@ -115,18 +125,18 @@ func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims)
// Try with azp (OIDC) // Try with azp (OIDC)
if len(payload.AuthorizedParty) > 0 { if len(payload.AuthorizedParty) > 0 {
if p, ok := c.Load(payload.AuthorizedParty); ok { if p, ok := c.LoadByTokenID(payload.AuthorizedParty); ok {
return p, ok return p, ok
} }
} }
// Try with tid (Azure) // Try with tid (Azure)
if payload.TenantID != "" { if payload.TenantID != "" {
if p, ok := c.Load(payload.TenantID); ok { if p, ok := c.LoadByTokenID(payload.TenantID); ok {
return p, ok return p, ok
} }
} }
// Fallback to aud // Fallback to aud
return c.Load(payload.Audience[0]) return c.LoadByTokenID(payload.Audience[0])
} }
// LoadByCertificate looks for the provisioner extension and extracts the // 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()) c.byID.Delete(p.GetID())
return errors.New("cannot add multiple provisioners with the same name") 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. // Store provisioner in byKey if EncryptedKey is defined.
if kid, _, ok := p.GetEncryptedKey(); ok { 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 // https://cloud.google.com/compute/docs/instances/verifying-instance-identity
type GCP struct { type GCP struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
ServiceAccounts []string `json:"serviceAccounts"` ServiceAccounts []string `json:"serviceAccounts"`
@ -96,6 +97,16 @@ type GCP struct {
// GetID returns the provisioner unique identifier. The name should uniquely // GetID returns the provisioner unique identifier. The name should uniquely
// identify any GCP provisioner. // identify any GCP provisioner.
func (p *GCP) GetID() string { 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 return "gcp/" + p.Name
} }

View file

@ -45,6 +45,12 @@ func (p *JWK) GetID() string {
if p.ID != "" { if p.ID != "" {
return 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 return p.Name + ":" + p.Key.KeyID
} }

View file

@ -42,6 +42,7 @@ type k8sSAPayload struct {
// entity trusted to make signature requests. // entity trusted to make signature requests.
type K8sSA struct { type K8sSA struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
PubKeys []byte `json:"publicKeys,omitempty"` PubKeys []byte `json:"publicKeys,omitempty"`
@ -56,6 +57,15 @@ type K8sSA struct {
// GetID returns the provisioner unique identifier. The name and credential id // GetID returns the provisioner unique identifier. The name and credential id
// should uniquely identify any K8sSA provisioner. // should uniquely identify any K8sSA provisioner.
func (p *K8sSA) GetID() string { 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 return K8sSAID
} }

View file

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

View file

@ -54,6 +54,7 @@ type openIDPayload struct {
// ClientSecret is mandatory, but it can be an empty string. // ClientSecret is mandatory, but it can be an empty string.
type OIDC struct { type OIDC struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
ClientID string `json:"clientID"` ClientID string `json:"clientID"`
@ -111,6 +112,15 @@ func sanitizeEmail(email string) string {
// GetID returns the provisioner unique identifier, the OIDC provisioner the // GetID returns the provisioner unique identifier, the OIDC provisioner the
// uses the clientID for this. // uses the clientID for this.
func (o *OIDC) GetID() string { 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 return o.ClientID
} }

View file

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

View file

@ -26,6 +26,7 @@ type sshPOPPayload struct {
// signature requests. // signature requests.
type SSHPOP struct { type SSHPOP struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Claims *Claims `json:"claims,omitempty"` Claims *Claims `json:"claims,omitempty"`
@ -38,6 +39,15 @@ type SSHPOP struct {
// GetID returns the provisioner unique identifier. The name and credential id // GetID returns the provisioner unique identifier. The name and credential id
// should uniquely identify any SSH-POP provisioner. // should uniquely identify any SSH-POP provisioner.
func (p *SSHPOP) GetID() string { 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 return "sshpop/" + p.Name
} }

View file

@ -26,6 +26,7 @@ type x5cPayload struct {
// signature requests. // signature requests.
type X5C struct { type X5C struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Roots []byte `json:"roots"` Roots []byte `json:"roots"`
@ -39,6 +40,15 @@ type X5C struct {
// GetID returns the provisioner unique identifier. The name and credential id // GetID returns the provisioner unique identifier. The name and credential id
// should uniquely identify any X5C provisioner. // should uniquely identify any X5C provisioner.
func (p *X5C) GetID() string { 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 return "x5c/" + p.Name
} }