diff --git a/authority/provisioner/acme.go b/authority/provisioner/acme.go index a36c496d..4109d217 100644 --- a/authority/provisioner/acme.go +++ b/authority/provisioner/acme.go @@ -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 } diff --git a/authority/provisioner/aws.go b/authority/provisioner/aws.go index 75115154..8a443554 100644 --- a/authority/provisioner/aws.go +++ b/authority/provisioner/aws.go @@ -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 } diff --git a/authority/provisioner/azure.go b/authority/provisioner/azure.go index ea8b08ec..b077d735 100644 --- a/authority/provisioner/azure.go +++ b/authority/provisioner/azure.go @@ -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 } diff --git a/authority/provisioner/collection.go b/authority/provisioner/collection.go index 23a4a2a0..ccfbc60a 100644 --- a/authority/provisioner/collection.go +++ b/authority/provisioner/collection.go @@ -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 :. - 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 { diff --git a/authority/provisioner/gcp.go b/authority/provisioner/gcp.go index 830e7965..6d19d052 100644 --- a/authority/provisioner/gcp.go +++ b/authority/provisioner/gcp.go @@ -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 } diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index a2d3e0b1..d57ff4c1 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -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 } diff --git a/authority/provisioner/k8sSA.go b/authority/provisioner/k8sSA.go index 209a7dd4..876131e1 100644 --- a/authority/provisioner/k8sSA.go +++ b/authority/provisioner/k8sSA.go @@ -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 } diff --git a/authority/provisioner/noop.go b/authority/provisioner/noop.go index ccdeccf4..18a38331 100644 --- a/authority/provisioner/noop.go +++ b/authority/provisioner/noop.go @@ -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 } diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index 46e1c623..df3c1f9e 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -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 } diff --git a/authority/provisioner/provisioner.go b/authority/provisioner/provisioner.go index 197bd26c..981d0b0a 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -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 { diff --git a/authority/provisioner/sshpop.go b/authority/provisioner/sshpop.go index 223f0b9e..dd9c7d1f 100644 --- a/authority/provisioner/sshpop.go +++ b/authority/provisioner/sshpop.go @@ -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 } diff --git a/authority/provisioner/x5c.go b/authority/provisioner/x5c.go index 2b05f4c8..9e47aaed 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -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 }