add keystone middleware for token validation
Change-Id: If3478342eaafbb61e6f99841d4930b9858dd23ac
This commit is contained in:
		
							
								
								
									
										46
									
								
								identity/middleware/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								identity/middleware/types.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | // Copyright (c) 2016 eBay Inc. | ||||||
|  | // | ||||||
|  | //    Licensed under the Apache License, Version 2.0 (the "License"); you may | ||||||
|  | //    not use this file except in compliance with the License. You may obtain | ||||||
|  | //    a copy of the License at | ||||||
|  | // | ||||||
|  | //         http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | // | ||||||
|  | //    Unless required by applicable law or agreed to in writing, software | ||||||
|  | //    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||||
|  | //    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||||
|  | //    License for the specific language governing permissions and limitations | ||||||
|  | //    under the License. | ||||||
|  |  | ||||||
|  | package middleware | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"git.openstack.org/openstack/golang-client.git/openstack" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Validator struct { | ||||||
|  | 	// Service account to talk to keystone | ||||||
|  | 	SvcAuthOpts openstack.AuthOpts | ||||||
|  | 	// File path the signing cert would be stored/cached | ||||||
|  | 	CachedSigningKeyPath string | ||||||
|  | 	TokenId              string | ||||||
|  | 	// Token revocation list memory cache duration | ||||||
|  | 	RevCacheDuration time.Duration | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Token revocation response structure | ||||||
|  | type revokeResp struct { | ||||||
|  | 	Signed string `json:"signed"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type revokedListCache struct { | ||||||
|  | 	Revoked []openstack.Token | ||||||
|  | 	// time when this cache was built | ||||||
|  | 	Time time.Time | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type RevokedList struct { | ||||||
|  | 	Revoked []openstack.Token | ||||||
|  | } | ||||||
							
								
								
									
										345
									
								
								identity/middleware/validation.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										345
									
								
								identity/middleware/validation.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,345 @@ | |||||||
|  | // Copyright (c) 2016 eBay Inc. | ||||||
|  | // | ||||||
|  | //    Licensed under the Apache License, Version 2.0 (the "License"); you may | ||||||
|  | //    not use this file except in compliance with the License. You may obtain | ||||||
|  | //    a copy of the License at | ||||||
|  | // | ||||||
|  | //         http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | // | ||||||
|  | //    Unless required by applicable law or agreed to in writing, software | ||||||
|  | //    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||||
|  | //    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||||
|  | //    License for the specific language governing permissions and limitations | ||||||
|  | //    under the License. | ||||||
|  |  | ||||||
|  | package middleware | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"compress/zlib" | ||||||
|  | 	"crypto/md5" | ||||||
|  | 	"crypto/x509" | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"encoding/pem" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"log" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"git.openstack.org/openstack/golang-client.git/openstack" | ||||||
|  | 	"github.com/fullsailor/pkcs7" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	PKI_ASN1_PREFIX = "MII" | ||||||
|  | 	PKIZ_PREFIX     = "PKIZ_" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Cache the token until it gets expired | ||||||
|  | var serviceTokenSession *openstack.Session | ||||||
|  |  | ||||||
|  | // Cache token revocation list until expired | ||||||
|  | var revocationListCache *revokedListCache | ||||||
|  |  | ||||||
|  | // NewValidator gets the credential for service account, token need to be validated, | ||||||
|  | // signing cert location (will store the cert from keystone if not there), and the | ||||||
|  | // revocation list cache duration (in seconds) and returns the validator. | ||||||
|  | func NewValidator(authOpts openstack.AuthOpts, token string, signingKeyPath string, revCacheSecs int) *Validator { | ||||||
|  | 	return &Validator{ | ||||||
|  | 		SvcAuthOpts:          authOpts, | ||||||
|  | 		CachedSigningKeyPath: signingKeyPath, | ||||||
|  | 		TokenId:              token, | ||||||
|  | 		RevCacheDuration:     time.Duration(revCacheSecs) * time.Second, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Validate does the local validation for PKI & PKIZ token and sends to keystone | ||||||
|  | // for other format tokens validation. It returns the extracted AuthToken struct | ||||||
|  | func (validator *Validator) Validate() (*openstack.AuthToken, error) { | ||||||
|  | 	var token *openstack.AuthToken | ||||||
|  |  | ||||||
|  | 	if strings.HasPrefix(validator.TokenId, PKIZ_PREFIX) || | ||||||
|  | 		strings.HasPrefix(validator.TokenId, PKI_ASN1_PREFIX) { | ||||||
|  | 		// do local validation for PKI and PKIZ token | ||||||
|  | 		if revocationListCache == nil || time.Now().Sub(revocationListCache.Time) > validator.RevCacheDuration { | ||||||
|  | 			_, err := validator.getRevocationList() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		access, err := validator.ValidateOffline() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err = json.Unmarshal(access, &token); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// set the token ID | ||||||
|  | 		token.Access.Token.ID = validator.TokenId | ||||||
|  |  | ||||||
|  | 		hashedTokenId := fmt.Sprintf("%x", md5.Sum([]byte(validator.TokenId))) | ||||||
|  | 		for _, rtoken := range revocationListCache.Revoked { | ||||||
|  | 			if rtoken.ID == hashedTokenId { | ||||||
|  | 				return nil, fmt.Errorf("token %s was revoked", hashedTokenId) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		access, err := validator.ValidateRemote() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err = json.Unmarshal(access, &token); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// validation should fail if token is expired | ||||||
|  | 	if token.Access.Token.Expires.Sub(time.Now()) < 0 { | ||||||
|  | 		return nil, fmt.Errorf("token %s is expired", validator.TokenId) | ||||||
|  | 	} | ||||||
|  | 	return token, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Validate the token locally without sending to keystone | ||||||
|  | // It take the token body and return the extracted access object as []byte | ||||||
|  | func (validator *Validator) ValidateOffline() ([]byte, error) { | ||||||
|  | 	token := "" | ||||||
|  | 	switch { | ||||||
|  | 	case strings.HasPrefix(validator.TokenId, PKIZ_PREFIX): | ||||||
|  | 		token = strings.TrimPrefix(validator.TokenId, PKIZ_PREFIX) | ||||||
|  | 		decompressedToken, err := decompressToken(token) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		token = trimCMSFormat(decompressedToken) | ||||||
|  | 	case strings.HasPrefix(validator.TokenId, PKI_ASN1_PREFIX): | ||||||
|  | 		token = validator.TokenId | ||||||
|  |  | ||||||
|  | 	default: | ||||||
|  | 		return nil, errors.New("can not validate offline, it has to be sent to keystone") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	decodedToken, err := base64DecodeFromCms(token) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	content, err := validator.checkSignature(decodedToken) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return content, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (validator *Validator) ValidateRemote() ([]byte, error) { | ||||||
|  | 	resp, err := validator.reqTokenValidation() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if resp.StatusCode == http.StatusUnauthorized { | ||||||
|  | 		// retry one more time using the new service token | ||||||
|  | 		err = validator.renewToken() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		resp, err = validator.reqTokenValidation() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	rbody, err := ioutil.ReadAll(resp.Body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, errors.New("error reading response body") | ||||||
|  | 	} | ||||||
|  | 	return rbody, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func decompressToken(token string) (string, error) { | ||||||
|  | 	decToken, err := base64.URLEncoding.DecodeString(token) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	zr, err := zlib.NewReader(bytes.NewBuffer(decToken)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	bb, err := ioutil.ReadAll(zr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return string(bb), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func base64DecodeFromCms(token string) ([]byte, error) { | ||||||
|  | 	t := strings.Replace(token, "-", "/", -1) | ||||||
|  | 	decToken, err := base64.StdEncoding.DecodeString(t) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return decToken, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // remove the customerized header and footer in PEM token | ||||||
|  | // -----BEGIN CMS----- | ||||||
|  | // -----END CMS----- | ||||||
|  | func trimCMSFormat(token string) string { | ||||||
|  | 	l := strings.Index(token, "\n") | ||||||
|  | 	r := strings.LastIndex(token, "\n") | ||||||
|  | 	t := token[l:r] | ||||||
|  | 	r2 := strings.LastIndex(t, "\n") | ||||||
|  | 	return t[0:r2] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Get the signging certificate from local dir | ||||||
|  | // It will get the cert from keystone if cache file does not exist | ||||||
|  | func (validator *Validator) getSigningCert() (*x509.Certificate, error) { | ||||||
|  | 	signPEM, err := ioutil.ReadFile(validator.CachedSigningKeyPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		resp, err := http.Get(validator.SvcAuthOpts.AuthUrl + "/certificates/signing") | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		if resp.StatusCode != 200 { | ||||||
|  | 			return nil, errors.New("can not get signing cert") | ||||||
|  | 		} | ||||||
|  | 		signPEM, err = ioutil.ReadAll(resp.Body) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		// cache the file to location | ||||||
|  | 		if err = ioutil.WriteFile(validator.CachedSigningKeyPath, []byte(signPEM), 0644); err != nil { | ||||||
|  | 			log.Println("error caching signging cert") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	block, _ := pem.Decode(signPEM) | ||||||
|  | 	if block == nil { | ||||||
|  | 		return nil, errors.New("can not decode PEM") | ||||||
|  | 	} | ||||||
|  | 	cert, err := x509.ParseCertificate(block.Bytes) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return cert, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // check the signature of the token | ||||||
|  | func (validator *Validator) checkSignature(data []byte) ([]byte, error) { | ||||||
|  | 	p7, err := pkcs7.Parse(data) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(p7.Signers) != 1 { | ||||||
|  | 		return nil, errors.New("should be only one signature found") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	signer := p7.Signers[0] | ||||||
|  | 	cert, err := validator.getSigningCert() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = cert.CheckSignature(x509.SHA256WithRSA, p7.Content, signer.EncryptedDigest) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return p7.Content, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // getRevocationList get a list of revoked tokens | ||||||
|  | func (validator *Validator) getRevocationList() ([]openstack.Token, error) { | ||||||
|  | 	// Get the service token to get the token revocation list | ||||||
|  | 	resp, err := validator.reqRevocationList() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if resp.StatusCode == http.StatusUnauthorized { | ||||||
|  | 		// try again when getting 401, it could be the cached token was revoked | ||||||
|  | 		// or the token got expired | ||||||
|  | 		validator.renewToken() | ||||||
|  | 		resp, err = validator.reqRevocationList() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	rbody, err := ioutil.ReadAll(resp.Body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	revokeCMSResp := revokeResp{} | ||||||
|  | 	if err = json.Unmarshal(rbody, &revokeCMSResp); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	revokeResp := trimCMSFormat(revokeCMSResp.Signed) | ||||||
|  | 	decodedResp, err := base64DecodeFromCms(revokeResp) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	revoked, err := validator.checkSignature(decodedResp) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	revokedList := RevokedList{} | ||||||
|  | 	if err = json.Unmarshal(revoked, &revokedList); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// update the revocation list cache | ||||||
|  | 	revocationListCache = &revokedListCache{ | ||||||
|  | 		Revoked: revokedList.Revoked, | ||||||
|  | 		Time:    time.Now(), | ||||||
|  | 	} | ||||||
|  | 	return revokedList.Revoked, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // reqRevocationList sends the GET request to keystone and get response back | ||||||
|  | func (validator *Validator) reqRevocationList() (*http.Response, error) { | ||||||
|  | 	if serviceTokenSession == nil { | ||||||
|  | 		validator.renewToken() | ||||||
|  | 	} | ||||||
|  | 	// Get token revocation list | ||||||
|  | 	return serviceTokenSession.Request("GET", validator.SvcAuthOpts.AuthUrl+"/tokens/revoked", nil, nil, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (validator *Validator) reqTokenValidation() (*http.Response, error) { | ||||||
|  | 	if serviceTokenSession == nil { | ||||||
|  | 		validator.renewToken() | ||||||
|  | 	} | ||||||
|  | 	// Get token revocation list | ||||||
|  | 	reqUrl := fmt.Sprintf("%s/tokens/%s", validator.SvcAuthOpts.AuthUrl, validator.TokenId) | ||||||
|  | 	return serviceTokenSession.Request("GET", reqUrl, nil, nil, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // renewToken gets the keystone token from service AuthOpts | ||||||
|  | func (validator *Validator) renewToken() error { | ||||||
|  | 	// Get the service token to get the token revocation list | ||||||
|  | 	auth, err := openstack.DoAuthRequest(validator.SvcAuthOpts) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Make a new client with these creds | ||||||
|  | 	serviceTokenSession, err = openstack.NewSession(nil, auth, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										87
									
								
								identity/middleware/validation_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								identity/middleware/validation_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | // Copyright (c) 2016 eBay Inc. | ||||||
|  | // | ||||||
|  | //    Licensed under the Apache License, Version 2.0 (the "License"); you may | ||||||
|  | //    not use this file except in compliance with the License. You may obtain | ||||||
|  | //    a copy of the License at | ||||||
|  | // | ||||||
|  | //         http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | // | ||||||
|  | //    Unless required by applicable law or agreed to in writing, software | ||||||
|  | //    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||||
|  | //    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||||
|  | //    License for the specific language governing permissions and limitations | ||||||
|  | //    under the License. | ||||||
|  |  | ||||||
|  | package middleware | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"git.openstack.org/openstack/golang-client.git/openstack" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	cacheKeyPath = "/tmp/signing.PEM" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func getAuthOpts() openstack.AuthOpts { | ||||||
|  | 	return openstack.AuthOpts{ | ||||||
|  | 		AuthUrl:  "http://localhost:5000/v2.0", | ||||||
|  | 		Project:  "demo", | ||||||
|  | 		Username: "demo", | ||||||
|  | 		Password: "demo", | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestVerifyLocalPKIZ(t *testing.T) { | ||||||
|  | 	token := `PKIZ_eJxdVduSqjoQfecrzvvUruEijj6GWwRNGBQC4U3AgXDTGVQuX78jzpy9z7EqVdJ091qru9P8-sV_mglt_I-ODo-HXwKybVMB1NmeY7u4pxh4qX7mtsnXdQ1VOvDMwfDBTsvrvKhyLfaQCXKzz3P0qeXVp1BUDK57UdN6mHNjotm2YSkdoyEu4whPdnnOT6MjJvK1Tpi9tDcdO3J7FpIqki1ReDjYpd0jn16REfCDA-RXS-yDJfLzG_bNHk25bLM-txuySGE9xhFiLgMMlWAQMBN7fODH8gbXP0vIOA-udWY7HbBj6M2OKRwKKpNzIg9VHNnzuwySGw3VJwNOsUyUZ9YYkm-0fZ3KeDxGmngM17eHX9KSLtFnJjzY6WIOILhME9OW1H8jIogGXCLV9R1GZVxjIxjiMhfxxOX4cY1CU0E-EF0jZ8JMp9XKuRYcNWN2Z-sOR5DqH1sSrtjH4YFsSTGsxUiuq3TkBbU6JiCzGo6lLWOlPtBgJXlS3PpV7RFLI9noZHNgi-u0jS-8DgaN9kUC1-0jeci7JcShWqVwfUna_fRjpOH-egxVElT9LOm42Yupcb7v5OFOZas7wvWUGecJ-0gS8LiWEVv0c-Fap47l-j73W3emDFptfJgl9Rkv2jHE_yZ9FrquvucAVxjihvrBSENPdn1voBNhbkhqPKUqLrMKTUhxDYvZrZQ96vbDXHhQj-ThkjTXpwRJfCCKp0j7LmJdcWfxGO3VJ6paJGHwlCYTVUiV_T1pAvZB5kAplcn4PRvz_6SxrlzGQxJPJN2_pd4TSGZg4X_Il1h_ONsTLp2Kyp6MIZVjvxJx6Sm01Gpc1txuL1wDl3SyJ8HV5yKNfEzrZ2-vb3_T9ENypQ1n1XIAfpH-YlTEUOIMmLOODv_Vxsf5mozO-lGsJCQib2uRQZO_03jymlO3qiSs5_EWkP5H0yxltNW4Cca4oWocBiK_iSItzYXr7wvXBws8FRWSAxU1TkH51AqcSoNCXGCjYLGRirFh87OvKQ_AvtfTho7I8HgSr0c6n8xIWiNqawVfOICausA3Th8ZWoS8rtc9ahDPg2bvkGAyXQR6CKTA1EFvkVCd4sgbjBIgLcdEAynSCLkJvFUi8ha9AebgrQEISfjsZ4ZJkUbnBKDv3zMlU3Z8ofBL1icwuFF5feWrTRMQ6KAe6vAArBr0ng58gB-77mfVcYYWAAe-G_WCnrqRmp_Wockkpwx39CV0BZvhXLZ2b2R4lWm1ircIOjp6Z8Dp9Oh4wtsV7PL0vicvm3fn86gV7eErk3R7a42bdukHggOOh6F6Wb1btCPZdoViT_psEaSAeY6rh4ssjr6KF602S_08sZ7mUA6i5uYaBdRongkrkOpKkOUsqqRop1b5eXz9APS1BRejWYr4a2dU4qbQGnyzbu5YFiT3P5rUNUv1w98ub4L0vowcxdJEiymnfLOXqvtp97rb7_F60jbs6y2kfgmvqvylti8Ef8rvO3OZv3pfLBET1r0IoyeudBj24IK2NLSNKhmUj9gRr_gmS6feGRdLaVGSLdhWa2Le3tCFdxePi1twmNz3ggnz98nExp9v1W82DTmf` | ||||||
|  | 	validator := NewValidator(getAuthOpts(), token, cacheKeyPath, 6) | ||||||
|  | 	access, err := validator.Validate() | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	fmt.Println(access) | ||||||
|  | 	project := access.Access.Token.Project | ||||||
|  | 	if project.Name != "demo" { | ||||||
|  | 		t.Fail() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestVerifyLocalPKI(t *testing.T) { | ||||||
|  | 	token := `MIIE3AYJKoZIhvcNAQcCoIIEzTCCBMkCAQExDTALBglghkgBZQMEAgEwggMqBgkqhkiG9w0BBwGgggMbBIIDF3siYWNjZXNzIjogeyJ0b2tlbiI6IHsiaXNzdWVkX2F0IjogIjIwMTYtMDUtMDNUMTk6NTE6MTIuNTM1NzQzIiwgImV4cGlyZXMiOiAiMjAxNi0wNS0wNFQxOTo1MToxMloiLCAiaWQiOiAicGxhY2Vob2xkZXIiLCAidGVuYW50IjogeyJjb3MiOiAiZGV2IiwgImRlc2NyaXB0aW9uIjogbnVsbCwgImVuYWJsZWQiOiB0cnVlLCAiaWQiOiAiMGMxNjM5OTJiY2NlNDUxZjg0NzEwMTZlMWE3MTA0ODgiLCAidnBjIjogImRldiIsICJuYW1lIjogImRlbW8ifSwgImF1ZGl0X2lkcyI6IFsiS01WV3F1U3NSYkdaTEc1Q0E2YnE2ZyJdfSwgInNlcnZpY2VDYXRhbG9nIjogW3siZW5kcG9pbnRzIjogW3siYWRtaW5VUkwiOiAiaHR0cDovL2xvY2FsaG9zdDozNTM1Ny92Mi4wIiwgInJlZ2lvbiI6ICJzdGFnZSIsICJwdWJsaWNVUkwiOiAiIiwgImlkIjogIjNkNGNmYTUyYWQ2OTQxYzViOWVlNzc5NjdkMzM3ODFiIn1dLCAiZW5kcG9pbnRzX2xpbmtzIjogW10sICJ0eXBlIjogImlkZW50aXR5IiwgIm5hbWUiOiAia2V5c3RvbmUifV0sICJ1c2VyIjogeyJ1c2VybmFtZSI6ICJkZW1vIiwgInJvbGVzX2xpbmtzIjogW10sICJpZCI6ICIzNjJkY2Q2NGY2ZTk0NjQ3YjBlNjlkY2I4ODNjYzIzOCIsICJyb2xlcyI6IFt7Im5hbWUiOiAiTWVtYmVyIn0sIHsibmFtZSI6ICJhZG1pbiJ9XSwgIm5hbWUiOiAiZGVtbyJ9LCAibWV0YWRhdGEiOiB7ImlzX2FkbWluIjogMCwgInJvbGVzIjogWyI5ZmUyZmY5ZWU0Mzg0YjE4OTRhOTA4NzhkM2U5MmJhYiIsICJmMWNhNDhiZDc0ZDI0ZDRlYTRhNTQwYmYyMDQ0YjQwMCJdfX19MYIBhTCCAYECAQEwXDBXMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVW5zZXQxDjAMBgNVBAcMBVVuc2V0MQ4wDAYDVQQKDAVVbnNldDEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tAgEBMAsGCWCGSAFlAwQCATANBgkqhkiG9w0BAQEFAASCAQBKR9e1K9TYOanIHpoxpCwKjnFY1Ue66+GbKVr956TLA+d2Q82IS-vJpmGRdxZh0t05knoErJEwjaq2XtA2subfIPnX6zm34y6Q1f8AJXDUowWX8YeeyRs548oCdaHoE1ak81jGOzYjMhZc-kljUlEDE4ejlO4wkxCnagDiA7uaRJgmSzB2kuuKZeeMxhlTe78tkoco3a1gZCGjGsUuEzbH5HU6RSugI5uxUGyMW0PS2j4K2+BBq2Uk-nHX0pIb513NOoDZztVq6ZuYx3KPIe-h29IMzoqL9OcZ4JH49ehzlDlTw8otu8wS8JUaIv7HNnGgJbCbsUmQOPvWju89rB3k` | ||||||
|  | 	validator := NewValidator(getAuthOpts(), token, cacheKeyPath, 6) | ||||||
|  | 	access, err := validator.Validate() | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	fmt.Println(access) | ||||||
|  | 	project := access.Access.Token.Project | ||||||
|  | 	if project.Name != "demo" { | ||||||
|  | 		t.Fail() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestVerifyLocalUUID(t *testing.T) { | ||||||
|  | 	token := `399789012f4fbedc63c55396f59654d6` | ||||||
|  | 	validator := NewValidator(getAuthOpts(), token, cacheKeyPath, 120) | ||||||
|  | 	access, err := validator.Validate() | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	fmt.Println(access) | ||||||
|  | 	project := access.Access.Token.Project | ||||||
|  | 	if project.Name != "demo" { | ||||||
|  | 		t.Fail() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Should be half traffic sending to keystone, not every time | ||||||
|  | func TestCache(t *testing.T) { | ||||||
|  | 	for i := 0; i < 4; i++ { | ||||||
|  | 		TestVerifyLocalPKIZ(t) | ||||||
|  | 		TestVerifyLocalPKI(t) | ||||||
|  | 		time.Sleep(3 * time.Second) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -20,6 +20,7 @@ import ( | |||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
|  | 	"net/http" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
| @@ -78,7 +79,10 @@ func DoAuthRequest(authopts AuthOpts) (AuthRef, error) { | |||||||
|  |  | ||||||
| 	path := auth_mod.AuthUrl + "/tokens" | 	path := auth_mod.AuthUrl + "/tokens" | ||||||
| 	body := auth_mod.JSON() | 	body := auth_mod.JSON() | ||||||
| 	resp, err := Post(path, nil, nil, &body) | 	headers := &http.Header{} | ||||||
|  | 	headers.Add("Content-Type", "application/json") | ||||||
|  |  | ||||||
|  | 	resp, err := Post(path, nil, headers, &body) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 arkxu
					arkxu