diff --git a/authority/authorize.go b/authority/authorize.go index 8d1f878a..69ad2a90 100644 --- a/authority/authorize.go +++ b/authority/authorize.go @@ -173,6 +173,9 @@ func (a *Authority) AuthorizeAdminToken(r *http.Request, token string) (*linkedc } // UseToken stores the token to protect against reuse. +// +// This method currently ignores any error coming from the GetTokenID, but it +// should specifically ignore the error provisioner.ErrAllowTokenReuse. func (a *Authority) UseToken(token string, prov provisioner.Interface) error { if reuseKey, err := prov.GetTokenID(token); err == nil { if reuseKey == "" { diff --git a/authority/provisioner/azure.go b/authority/provisioner/azure.go index 230f246f..fee50658 100644 --- a/authority/provisioner/azure.go +++ b/authority/provisioner/azure.go @@ -131,9 +131,10 @@ func (p *Azure) GetTokenID(token string) (string, error) { return "", errors.Wrap(err, "error verifying claims") } - // If TOFU is disabled create return the token kid + // If TOFU is disabled then allow token re-use. Azure caches the token for + // 24h and without allowing the re-use we cannot use it twice. if p.DisableTrustOnFirstUse { - return claims.ID, nil + return "", ErrAllowTokenReuse } sum := sha256.Sum256([]byte(claims.XMSMirID)) diff --git a/authority/provisioner/azure_test.go b/authority/provisioner/azure_test.go index f21a5676..8033d345 100644 --- a/authority/provisioner/azure_test.go +++ b/authority/provisioner/azure_test.go @@ -72,7 +72,7 @@ func TestAzure_GetTokenID(t *testing.T) { wantErr bool }{ {"ok", p1, args{t1}, w1, false}, - {"ok no TOFU", p2, args{t2}, "the-jti", false}, + {"ok no TOFU", p2, args{t2}, "", true}, {"fail token", p1, args{"bad-token"}, "", true}, {"fail claims", p1, args{"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey.fooo"}, "", true}, } diff --git a/authority/provisioner/provisioner.go b/authority/provisioner/provisioner.go index 83cc6946..75fabed5 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -4,6 +4,7 @@ import ( "context" "crypto/x509" "encoding/json" + stderrors "errors" "net/url" "regexp" "strings" @@ -32,6 +33,17 @@ type Interface interface { AuthorizeSSHRekey(ctx context.Context, token string) (*ssh.Certificate, []SignOption, error) } +// ErrAllowTokenReuse is an error that is returned by provisioners that allows +// the reuse of tokens. +// +// This is for example returned by the Azure provisioner when +// DisableTrustOnFirstUse is set to true. For AWS and GCP DisableTrustOnFirst +// use means that we allow the re-use of a token coming from a specific +// instance, but in these providers we can always get a new token, but because +// Azure caches the token for up to 24h we should add a mechanism to allow the +// re-use. +var ErrAllowTokenReuse = stderrors.New("allow token reuse") + // Audiences stores all supported audiences by request type. type Audiences struct { Sign []string diff --git a/authority/tls.go b/authority/tls.go index 4c3420df..a3dd95d3 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -366,7 +366,7 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error } rci.ProvisionerID = p.GetID() rci.TokenID, err = p.GetTokenID(revokeOpts.OTT) - if err != nil { + if err != nil && !errors.Is(err, provisioner.ErrAllowTokenReuse) { return errs.Wrap(http.StatusInternalServerError, err, "authority.Revoke; could not get ID for token") }