diff options
-rw-r--r-- | examples/pki/certs/cacert.pem | 20 | ||||
-rw-r--r-- | examples/pki/certs/middleware.pem | 44 | ||||
-rw-r--r-- | examples/pki/certs/signing_cert.pem | 16 | ||||
-rw-r--r-- | examples/pki/certs/ssl_cert.pem | 16 | ||||
-rw-r--r-- | examples/pki/cms/auth_token_revoked.pem | 8 | ||||
-rw-r--r-- | examples/pki/cms/auth_token_scoped.pem | 6 | ||||
-rw-r--r-- | examples/pki/cms/auth_token_unscoped.pem | 8 | ||||
-rw-r--r-- | examples/pki/cms/auth_v3_token_revoked.json | 11 | ||||
-rw-r--r-- | examples/pki/cms/auth_v3_token_revoked.pem | 44 | ||||
-rw-r--r-- | examples/pki/cms/auth_v3_token_scoped.json | 11 | ||||
-rw-r--r-- | examples/pki/cms/auth_v3_token_scoped.pem | 42 | ||||
-rw-r--r-- | examples/pki/cms/revocation_list.pem | 6 | ||||
-rwxr-xr-x | examples/pki/gen_pki.sh | 2 | ||||
-rw-r--r-- | examples/pki/private/cakey.pem | 28 | ||||
-rw-r--r-- | examples/pki/private/signing_key.pem | 28 | ||||
-rw-r--r-- | examples/pki/private/ssl_key.pem | 28 | ||||
-rw-r--r-- | keystoneclient/middleware/auth_token.py | 255 | ||||
-rw-r--r-- | tests/test_auth_token_middleware.py | 863 |
18 files changed, 1080 insertions, 356 deletions
diff --git a/examples/pki/certs/cacert.pem b/examples/pki/certs/cacert.pem index 8cf663c..6075f2e 100644 --- a/examples/pki/certs/cacert.pem +++ b/examples/pki/certs/cacert.pem @@ -1,18 +1,18 @@ -----BEGIN CERTIFICATE----- -MIIC0TCCAjqgAwIBAgIJAP2TNFqmE1KUMA0GCSqGSIb3DQEBBQUAMIGeMQowCAYD +MIIC0TCCAjqgAwIBAgIJAK6p/UfYvENdMA0GCSqGSIb3DQEBBQUAMIGeMQowCAYD VQQFEwE1MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN1bm55 dmFsZTESMBAGA1UEChMJT3BlblN0YWNrMREwDwYDVQQLEwhLZXlzdG9uZTElMCMG CSqGSIb3DQEJARYWa2V5c3RvbmVAb3BlbnN0YWNrLm9yZzEUMBIGA1UEAxMLU2Vs -ZiBTaWduZWQwIBcNMTIxMTExMTA1NDA2WhgPMjA3MTA1MDYxMDU0MDZaMIGeMQow +ZiBTaWduZWQwIBcNMTMwMzA3MTcxMzEyWhgPMjA3MTA4MzAxNzEzMTJaMIGeMQow CAYDVQQFEwE1MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN1 bm55dmFsZTESMBAGA1UEChMJT3BlblN0YWNrMREwDwYDVQQLEwhLZXlzdG9uZTEl MCMGCSqGSIb3DQEJARYWa2V5c3RvbmVAb3BlbnN0YWNrLm9yZzEUMBIGA1UEAxML -U2VsZiBTaWduZWQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMXgnd5wlHAp -GxZ58LrpEkHU995lT9PxtMgkp0tpFhg7R5HQw9K7TfQk5NHB28hNzf8UE/c0z2pJ -XggPnAzvdx27NQeJGX5CWsi6fITZ8vH/+SxgfxxC+CE/6BkDpzw21MgBtq11vWL7 -XVaxNeU12Ax889U66i3CrObuCYt2mbpzAgMBAAGjEzARMA8GA1UdEwEB/wQFMAMB -Af8wDQYJKoZIhvcNAQEFBQADgYEAkFIbnr2/0/XWp+f80Gl6GAC7tdmZFlT9udVF -q794rXyMlYY64pq34SzfQAn+4DztT4B9yzrTx03tLNr6Uf+5TS+ubcwG41UBBMs/ -Icf9zBMRqr+IXhijS49gQ7dPjqNTCqX+6ILbRWjdXP15ZWymI3ayQL/CMwFt/E+0 -kT6MLes= +U2VsZiBTaWduZWQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOw4quFzQ/xb +UOKuLtXdiZLPA0Wi38iGEa+T8tp7j3US44wAamckdZb4cq9/Qx03EBKd2mcJvUoP +rLnSlnHQMH2VGA1whZpZTWqt8ydQdDYB1SUKeUoxcjq8EKl8X8Sd3dP5amlyFCOI +GVhFyAXYgaYlmf+s6FIzpY55Uy2zX+nZAgMBAAGjEzARMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEFBQADgYEAp5nII86N8ISu2FGEW/Ja7zU0diZpv7h/8enR +06uwksv722ArOzQ22Y0xezZN3TEc5GVKPbHPSXfvvha09K5QlIp9idLy65Mu/DXa +Fo+kJoq7rMW6Det/mOoWp3O4zgYlxvKTFjyNo300nRir4nvHmbrF/vhXVqDm2roS +vLoyVvY= -----END CERTIFICATE----- diff --git a/examples/pki/certs/middleware.pem b/examples/pki/certs/middleware.pem index ae5b8db..4e840fa 100644 --- a/examples/pki/certs/middleware.pem +++ b/examples/pki/certs/middleware.pem @@ -3,31 +3,31 @@ MIICoTCCAgoCARAwDQYJKoZIhvcNAQEFBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNV BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQK EwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZDAgFw0x -MjExMTExMDU0MDZaGA8yMDcxMDUwNjEwNTQwNlowgZAxCzAJBgNVBAYTAlVTMQsw +MzAzMDcxNzEzMTJaGA8yMDcxMDgzMDE3MTMxMlowgZAxCzAJBgNVBAYTAlVTMQsw CQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3Rh Y2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv cGVuc3RhY2sub3JnMRIwEAYDVQQDEwlsb2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEB -BQADgY0AMIGJAoGBALVu4bjaOH33yAx0WdpEqj4UDVsLxVjWxEpIbOlDlc6IfJd+ -cUriQtxf6ahjxtzLPERS81SnwZmrICWZngbOn733pULMTZktTJH+o7C74NdKwUSN -xjlCeWUy+FqIQoje4ygoJRPpMdkp1wHNO0ZERwRN9e8M5TIlx/LRtk+q8bT5AgMB -AAEwDQYJKoZIhvcNAQEFBQADgYEAcp9ancue9Oq+MkaPucCrIqFhiUsdUThulJlB -etPpUDGgStBSHgze/oxG2+flIjRoI6gG9Chfw//vWHOwDT7N32AHSgaI4b8/k/+s -hAV2khYkV4PW2oS1TfeU/vxQzXbgApqhLBNqfFmJVW48aGAr/aqsJi3MYWN3269+ -6vChaVw= +BQADgY0AMIGJAoGBAM323GVGJ6UImf6nfz9P+9MURBo0okaV/3ewyfSMri8DbM0s +CqDtC43R1jIrHtEdnUU7kHguFXc09p9pHSRbblZ3TNUuZgfoLTNUUY5LETrXdlIQ +8WQDqUZq2kSbUBWYkHOYlzmowoWa2hKUC1ifHcleI2dVMW+LIkDhXPEc4XO1AgMB +AAEwDQYJKoZIhvcNAQEFBQADgYEAchynxfP/FQC8FNhKs/dGI196qBq4MVobvNjQ +trdLAjbZwp1/i6SHLxXEDm9bIWyInE7D8hGqXXQAImzAaH0t3oYR3C4XQWOSPPwU +6tamnsXDVR2w3aHbEh6AuIahZQaau5tnGopwiWRDNZllbSlfay60r6Vj4ex5LtVM +eBLz1Jg= -----END CERTIFICATE----- -----BEGIN PRIVATE KEY----- -MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALVu4bjaOH33yAx0 -WdpEqj4UDVsLxVjWxEpIbOlDlc6IfJd+cUriQtxf6ahjxtzLPERS81SnwZmrICWZ -ngbOn733pULMTZktTJH+o7C74NdKwUSNxjlCeWUy+FqIQoje4ygoJRPpMdkp1wHN -O0ZERwRN9e8M5TIlx/LRtk+q8bT5AgMBAAECgYAmwq6EYFJrTvE0//JmN/8qzfvg -dI5PoWpD+F8UInUxr2T2tHOdrOLd07vGVrKYXu7cJeCIOGKa4r02azAggioL/nE9 -FgPpqEC+QROvLuhFsk1gLZ2pGQ06sveKZVMH22h59BKZkYlhjh5qd4vlmhPqkmPp -gdXj7ZjDCJhhQdFVkQJBANp18k2mVksn8q29LMieVTSIZNN3ucDA1QHbim+3fp/O -GxCzU7Mv1Xfnu1zoRFu5/sF3YG0Zy3TGPDrEljBC3rUCQQDUnBjVFXL35OkBZqXW -taJPzGbsPoqAO+Ls2juS97zNzeGxUNhvcKuEvHO63PXqDxp1535DpvJEBN1rT2FF -iaO1AkEAt/QTWWFUTqrPxY6DNFdm5fpn9E1fg7icZJkKBDJeFJCH59MpCryfovzl -n0ERtq9ynlQ4RQYwdR8rvkylLvRP9QJAOiXHFOAc5XeR0nREfwiGL9TzgUFJl/DJ -C4ZULMnctVzNkTVPPItQHal87WppR26CCiUZ/161e6zo8eRv8hjG0QJABWqfYQuK -dWH8nxlXS+NFUDbsCdL+XpOVE7iEH7hvSw/A/kz40mLx8sDp/Fz1ysrogR/L+NGC -Vrlwm4q/WYJO0Q== +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAM323GVGJ6UImf6n +fz9P+9MURBo0okaV/3ewyfSMri8DbM0sCqDtC43R1jIrHtEdnUU7kHguFXc09p9p +HSRbblZ3TNUuZgfoLTNUUY5LETrXdlIQ8WQDqUZq2kSbUBWYkHOYlzmowoWa2hKU +C1ifHcleI2dVMW+LIkDhXPEc4XO1AgMBAAECgYBOZfMKkaOxjA6iAjvLa7Sdag9q +MjK6z4nIk4CsF4iN2K3ngyYgj1pgh0kTG5rFWpJssfmR5WjCUWS21RoEptDeZf/A +jRqzW3u493JAhyOjTK4DYbB9CwCmeGkoImC3nn2PrBgG1OPrSQMB3ODqVA2Pa1eF +omqKQmAqCCijtmllmQJBAOnpN3sjykUlGVWY7HxdBAOsQ5DkkCXL6ZSjA3pRYvJQ +12pKELZyxZ8GtVCFvOjaCpdxL+1MsRHkEfZpWz9o9BsCQQDhagjUFbgAQzo/TH1X +iblrnWUi7rs+IIDOF48qy/t1FKFlyCHbMYQLB/rPSN1G+5uMEapCuOBpVQsO9v5n +wJRvAkBQXOPG1sEDiH9vvR5ii8J5UJHWEfDES45wlqD3QUbxYXzg85lSVZQ30qIw +jAIfLeo9pZGFwbeEIgtZ0VCcNH7JAkBK3FEkRjY+eBUvEnMKEGYw9CuzZz9uCZNd +Xnughe/z2S8kw0tjJVWp6DOGhbdfLI5i/TbjQ8zbjm/Gv4aL5GwnAkEA42UWJKNQ +ztq73xmVmihToMjMe6k2DDPQpq+e2b/522Vz1ZDJlIV9tpoykFX2XiPnRz1o1oWd +DXQBvYeFzthvKA== -----END PRIVATE KEY----- diff --git a/examples/pki/certs/signing_cert.pem b/examples/pki/certs/signing_cert.pem index 1491b55..a3b141e 100644 --- a/examples/pki/certs/signing_cert.pem +++ b/examples/pki/certs/signing_cert.pem @@ -3,15 +3,15 @@ MIICoDCCAgkCAREwDQYJKoZIhvcNAQEFBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNV BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQK EwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZDAgFw0x -MjExMTExMDU0MDZaGA8yMDcxMDUwNjEwNTQwNlowgY8xCzAJBgNVBAYTAlVTMQsw +MzAzMDcxNzEzMTJaGA8yMDcxMDgzMDE3MTMxMlowgY8xCzAJBgNVBAYTAlVTMQsw CQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3Rh Y2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv cGVuc3RhY2sub3JnMREwDwYDVQQDEwhLZXlzdG9uZTCBnzANBgkqhkiG9w0BAQEF -AAOBjQAwgYkCgYEAuoQC6IBqMxC5845c/ZkLsdcQbTHqIpYJHEkwEoxyeEjwiGFf -iZmiZ91pSFNc9MfjdJnN+be/ndVS19w1nrrJvV/udVsf6JZWkTPX5HyxnllwznCH -pP7gfvMZzGsqzWlSdiD6mcRbCYRX9hCCauG3jhCtISINCVYMYQGH6QSib9sCAwEA -ATANBgkqhkiG9w0BAQUFAAOBgQBCssELi+1RSjEmzeqSnpgUqmtpvB9oxbcwl+xH -rIrYvqMU6pV2aSxgLDqpGjjusLHUau9Bmu3Myc/fm9/mlPUQHNj0AWl8vvfSlq1b -vsWMUa1h4UFlPWoF2DIUFd+noBxe5CbcLUV6K0oyJAcPO433OyuGl5oQkhxmoy1J -w59KRg== +AAOBjQAwgYkCgYEApibRgDiDl4u73oeVQjkiNBN+VYYSQ82UJoQvuoYbzYndAik9 +P63vf42lu2tSMs8U/oNl/EqHvI92rZhGpzr9wRVAQuaKYlrPk1Sn9hJHFjjotSHY +Sq+ivlG7WmLoIrQkYYzFr3r+yiiYtzL0cv68objKEwGMZasn95nJSjqAxFUCAwEA +ATANBgkqhkiG9w0BAQUFAAOBgQAmFHIcvPC3G+DFM8Ke8kZi/UGl4ugUlkdIVmCG +yokdR0b7v72r8ocQ/QSIRcw/Y0t3lPsAt1Dq6m2zN8PAC30m4QQqCu4o1xEWU51N +sUfNaw55qjpYEpZ2DmUjJc0kzYIsmaDkqM4t5lTJ7K7+zoWdW9joJV+VAyEq6NiS +RhjOeg== -----END CERTIFICATE----- diff --git a/examples/pki/certs/ssl_cert.pem b/examples/pki/certs/ssl_cert.pem index 0a0bc21..784638f 100644 --- a/examples/pki/certs/ssl_cert.pem +++ b/examples/pki/certs/ssl_cert.pem @@ -3,15 +3,15 @@ MIICoTCCAgoCARAwDQYJKoZIhvcNAQEFBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNV BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQK EwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZDAgFw0x -MjExMTExMDU0MDZaGA8yMDcxMDUwNjEwNTQwNlowgZAxCzAJBgNVBAYTAlVTMQsw +MzAzMDcxNzEzMTJaGA8yMDcxMDgzMDE3MTMxMlowgZAxCzAJBgNVBAYTAlVTMQsw CQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3Rh Y2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv cGVuc3RhY2sub3JnMRIwEAYDVQQDEwlsb2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEB -BQADgY0AMIGJAoGBALVu4bjaOH33yAx0WdpEqj4UDVsLxVjWxEpIbOlDlc6IfJd+ -cUriQtxf6ahjxtzLPERS81SnwZmrICWZngbOn733pULMTZktTJH+o7C74NdKwUSN -xjlCeWUy+FqIQoje4ygoJRPpMdkp1wHNO0ZERwRN9e8M5TIlx/LRtk+q8bT5AgMB -AAEwDQYJKoZIhvcNAQEFBQADgYEAcp9ancue9Oq+MkaPucCrIqFhiUsdUThulJlB -etPpUDGgStBSHgze/oxG2+flIjRoI6gG9Chfw//vWHOwDT7N32AHSgaI4b8/k/+s -hAV2khYkV4PW2oS1TfeU/vxQzXbgApqhLBNqfFmJVW48aGAr/aqsJi3MYWN3269+ -6vChaVw= +BQADgY0AMIGJAoGBAM323GVGJ6UImf6nfz9P+9MURBo0okaV/3ewyfSMri8DbM0s +CqDtC43R1jIrHtEdnUU7kHguFXc09p9pHSRbblZ3TNUuZgfoLTNUUY5LETrXdlIQ +8WQDqUZq2kSbUBWYkHOYlzmowoWa2hKUC1ifHcleI2dVMW+LIkDhXPEc4XO1AgMB +AAEwDQYJKoZIhvcNAQEFBQADgYEAchynxfP/FQC8FNhKs/dGI196qBq4MVobvNjQ +trdLAjbZwp1/i6SHLxXEDm9bIWyInE7D8hGqXXQAImzAaH0t3oYR3C4XQWOSPPwU +6tamnsXDVR2w3aHbEh6AuIahZQaau5tnGopwiWRDNZllbSlfay60r6Vj4ex5LtVM +eBLz1Jg= -----END CERTIFICATE----- diff --git a/examples/pki/cms/auth_token_revoked.pem b/examples/pki/cms/auth_token_revoked.pem index 0bd7a70..6136f6d 100644 --- a/examples/pki/cms/auth_token_revoked.pem +++ b/examples/pki/cms/auth_token_revoked.pem @@ -35,8 +35,8 @@ ZXJuYW1lMSJ9fX0NCjGCAUkwggFFAgEBMIGkMIGeMQowCAYDVQQFEwE1MQswCQYD VQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN1bm55dmFsZTESMBAGA1UE ChMJT3BlblN0YWNrMREwDwYDVQQLEwhLZXlzdG9uZTElMCMGCSqGSIb3DQEJARYW a2V5c3RvbmVAb3BlbnN0YWNrLm9yZzEUMBIGA1UEAxMLU2VsZiBTaWduZWQCAREw -BwYFKw4DAhowDQYJKoZIhvcNAQEBBQAEgYBhV5KrVjcdACPUNafkPY+lgCSlh6uc -N55SATBcQmg1/argEUFg/cx2GcF7ftQV384iGepLEgsq+6om2wPw6DWA0RknpVLJ -vMsHbWdGoXIZ5jRuAQTPtkXcJQOR677baDHvGJ+5zwBBDT2CmN2Tcv348+Xpjp7D -hF/cmAXnYYo00g== +BwYFKw4DAhowDQYJKoZIhvcNAQEBBQAEgYB3HICZ2Jj9edPkhmic5Td/qzod2FpQ +tB5EUL32Qw33FrMo6ALxG2znmiR3F2rf2kSmOVpBRQgysnkVXjDGPuBt/qMq41VR +NvvoM+Cf2HtDYGFvyyO3QNRf9NLaFije71pRQUBFR8iEz0zjvdouyuHVZsbQuke5 +XdEgB8F3fQ6/Pg== -----END CMS----- diff --git a/examples/pki/cms/auth_token_scoped.pem b/examples/pki/cms/auth_token_scoped.pem index 529d7f8..20c6929 100644 --- a/examples/pki/cms/auth_token_scoped.pem +++ b/examples/pki/cms/auth_token_scoped.pem @@ -35,7 +35,7 @@ AQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTES MBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3RhY2sxETAPBgNVBAsT CEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVuc3RhY2sub3Jn MRQwEgYDVQQDEwtTZWxmIFNpZ25lZAIBETAHBgUrDgMCGjANBgkqhkiG9w0BAQEF -AASBgFizBVs3dCvlHx04nUHgXHpaA9RL+e3uaaNszK9UwCBpBlv8c6+74sz6i3+G -eYDIpL9bc6QgNJ6cKhmW5yLmS8/+mmAMAcm06bdWc7p/mqC3Ild+xmQ+OHDYyyJg -DvtRUgtidFUCvxne/nwKK0WHJlpY+iwWqel5F+Xqmb8vheb1 +AASBgFbBja47P7p32dQ+wAXKDn9/JL/RjImAKvT/f8bBZxmc+SbnmpDd0lwH44eE +cVFfq55Ny0+SmYaLP6ZgtvGYpiP9TqxuySHQP1EKxAmIFA2yRa3YTviTsSvH0OCC +WEnlYLxxdqh97whF3H5bDOMh6aVEyHPRS2m8oOqcPW+5o4gX -----END CMS----- diff --git a/examples/pki/cms/auth_token_unscoped.pem b/examples/pki/cms/auth_token_unscoped.pem index d42d1f1..1c284a1 100644 --- a/examples/pki/cms/auth_token_unscoped.pem +++ b/examples/pki/cms/auth_token_unscoped.pem @@ -10,8 +10,8 @@ dXNlcl9uYW1lMSJ9fX0xggFJMIIBRQIBATCBpDCBnjEKMAgGA1UEBRMBNTELMAkG A1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdW5ueXZhbGUxEjAQBgNV BAoTCU9wZW5TdGFjazERMA8GA1UECxMIS2V5c3RvbmUxJTAjBgkqhkiG9w0BCQEW FmtleXN0b25lQG9wZW5zdGFjay5vcmcxFDASBgNVBAMTC1NlbGYgU2lnbmVkAgER -MAcGBSsOAwIaMA0GCSqGSIb3DQEBAQUABIGAITCwkW7cAbcWbCBD5GfGMGHB9hP/ -UagaCZ8HFhlzjdQoJjvC+Mtu+3lWlwqPGR8ztY9kBc1401S2qJxD4FGo+M3CkNpF -s0mtaT2PUJfFkDCzHqeBQNFHyZeqLjkPYnokPcw4s3i60DBGTFfAiUT3xumn8a4h -C+zEAee35C/A+Iw= +MAcGBSsOAwIaMA0GCSqGSIb3DQEBAQUABIGARUpIQsA8a2g9HC1ZjpX37oXZz/3n +hdpRUyKTWbjd2mi2rC68DVnyHLhZ3SQfBN896fPG1HW6LFuFrserYwBYVrX1rGDz +OS2dBigPzeP1301X3IRdbDrnMvzmEX2eTSmBEZ/CMMOXTTSYAYutyOtzATW9v639 +rRT4L/yQFcIxfCo= -----END CMS----- diff --git a/examples/pki/cms/auth_v3_token_revoked.json b/examples/pki/cms/auth_v3_token_revoked.json new file mode 100644 index 0000000..0807f86 --- /dev/null +++ b/examples/pki/cms/auth_v3_token_revoked.json @@ -0,0 +1,11 @@ +{"token": + {"catalog": [{"endpoints": [{"adminURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a"}], "endpoints_links": [], "type": "volume", "name": "volume"}, + {"endpoints": [{"adminURL": "http://127.0.0.1:9292/v1", "region": "regionOne", "internalURL": "http://127.0.0.1:9292/v1", "publicURL": "http://127.0.0.1:9292/v1"}], "endpoints_links": [], "type": "image", "name": "glance"}, + {"endpoints": [{"adminURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a"}], "endpoints_links": [], "type": "compute", "name": "nova"}, + {"endpoints": [{"adminURL": "http://127.0.0.1:35357/v3", "region": "RegionOne", "internalURL": "http://127.0.0.1:35357/v3", "publicURL": "http://127.0.0.1:5000/v3"}], "endpoints_links": [], "type": "identity", "name": "keystone"}], + "expires": "2012-06-02T14:47:34Z", + "project": {"enabled": true, "description": null, "name": "tenant_name1", "id": "tenant_id1", "domain": {"id": "domain_id1", "name": "domain_name1"}}, + "user": {"name": "revoked_username1", "id": "revoked_user_id1", "domain": {"id": "domain_id1", "name": "domain_name1"}}, + "roles": [{"name": "role1"}, {"name": "role2"}] + } +} diff --git a/examples/pki/cms/auth_v3_token_revoked.pem b/examples/pki/cms/auth_v3_token_revoked.pem new file mode 100644 index 0000000..02e73b0 --- /dev/null +++ b/examples/pki/cms/auth_v3_token_revoked.pem @@ -0,0 +1,44 @@ +-----BEGIN CMS----- +MIIHsgYJKoZIhvcNAQcCoIIHozCCB58CAQExCTAHBgUrDgMCGjCCBkAGCSqGSIb3 +DQEHAaCCBjEEggYteyJ0b2tlbiI6DQogICAgeyJjYXRhbG9nIjogW3siZW5kcG9p +bnRzIjogW3siYWRtaW5VUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0 +YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwgInJlZ2lvbiI6ICJyZWdp +b25PbmUiLCAiaW50ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc2L3Yx +LzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwgInB1YmxpY1VSTCI6 +ICJodHRwOi8vMTI3LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBm +Y2Y4OWJiNjYxN2EifV0sICJlbmRwb2ludHNfbGlua3MiOiBbXSwgInR5cGUiOiAi +dm9sdW1lIiwgIm5hbWUiOiAidm9sdW1lIn0sDQogICAgICAgICAgICAgICAgIHsi +ZW5kcG9pbnRzIjogW3siYWRtaW5VUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo5Mjky +L3YxIiwgInJlZ2lvbiI6ICJyZWdpb25PbmUiLCAiaW50ZXJuYWxVUkwiOiAiaHR0 +cDovLzEyNy4wLjAuMTo5MjkyL3YxIiwgInB1YmxpY1VSTCI6ICJodHRwOi8vMTI3 +LjAuMC4xOjkyOTIvdjEifV0sICJlbmRwb2ludHNfbGlua3MiOiBbXSwgInR5cGUi +OiAiaW1hZ2UiLCAibmFtZSI6ICJnbGFuY2UifSwNCiAgICAgICAgICAgICAgICAg +eyJlbmRwb2ludHMiOiBbeyJhZG1pblVSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3 +NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsICJyZWdp +b24iOiAicmVnaW9uT25lIiwgImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4w +LjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwg +InB1YmxpY1VSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNm +YmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSJ9XSwgImVuZHBvaW50c19saW5rcyI6 +IFtdLCAidHlwZSI6ICJjb21wdXRlIiwgIm5hbWUiOiAibm92YSJ9LA0KICAgICAg +ICAgICAgICAgICB7ImVuZHBvaW50cyI6IFt7ImFkbWluVVJMIjogImh0dHA6Ly8x +MjcuMC4wLjE6MzUzNTcvdjMiLCAicmVnaW9uIjogIlJlZ2lvbk9uZSIsICJpbnRl +cm5hbFVSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YzIiwgInB1YmxpY1VS +TCI6ICJodHRwOi8vMTI3LjAuMC4xOjUwMDAvdjMifV0sICJlbmRwb2ludHNfbGlu +a3MiOiBbXSwgInR5cGUiOiAiaWRlbnRpdHkiLCAibmFtZSI6ICJrZXlzdG9uZSJ9 +XSwNCiAgICAgImV4cGlyZXMiOiAiMjAxMi0wNi0wMlQxNDo0NzozNFoiLA0KICAg +ICAicHJvamVjdCI6IHsiZW5hYmxlZCI6IHRydWUsICJkZXNjcmlwdGlvbiI6IG51 +bGwsICJuYW1lIjogInRlbmFudF9uYW1lMSIsICJpZCI6ICJ0ZW5hbnRfaWQxIiwg +ImRvbWFpbiI6IHsiaWQiOiAiZG9tYWluX2lkMSIsICJuYW1lIjogImRvbWFpbl9u +YW1lMSJ9fSwNCiAgICAgInVzZXIiOiB7Im5hbWUiOiAicmV2b2tlZF91c2VybmFt +ZTEiLCAiaWQiOiAicmV2b2tlZF91c2VyX2lkMSIsICJkb21haW4iOiB7ImlkIjog +ImRvbWFpbl9pZDEiLCAibmFtZSI6ICJkb21haW5fbmFtZTEifX0sDQogICAgICJy +b2xlcyI6IFt7Im5hbWUiOiAicm9sZTEifSwgeyJuYW1lIjogInJvbGUyIn1dDQog +ICAgfQ0KfQ0KMYIBSTCCAUUCAQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYT +AlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlP +cGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlz +dG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZAIBETAHBgUr +DgMCGjANBgkqhkiG9w0BAQEFAASBgD8S/YsERhsYgxNTHQ+AohaWBCxS2eMJDG1e +lZBabrMHra5DkP5PAeHKApcagNo4UfcN9dVeGFi+VzHD/lLHaR1r1VI0SiSb+pQ4 +dTZGEtMVsfPbReWS9RaLt3YePGkZ410Nhx2STF1kmMmVhGGXDzyMbIGGQu6BmQsF +G8+izx9v +-----END CMS----- diff --git a/examples/pki/cms/auth_v3_token_scoped.json b/examples/pki/cms/auth_v3_token_scoped.json new file mode 100644 index 0000000..23ad0e3 --- /dev/null +++ b/examples/pki/cms/auth_v3_token_scoped.json @@ -0,0 +1,11 @@ +{"token": + {"catalog": [{"endpoints": [{"adminURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a"}], "endpoints_links": [], "type": "volume", "name": "volume"}, + {"endpoints": [{"adminURL": "http://127.0.0.1:9292/v1", "region": "regionOne", "internalURL": "http://127.0.0.1:9292/v1", "publicURL": "http://127.0.0.1:9292/v1"}], "endpoints_links": [], "type": "image", "name": "glance"}, + {"endpoints": [{"adminURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a"}], "endpoints_links": [], "type": "compute", "name": "nova"}, + {"endpoints": [{"adminURL": "http://127.0.0.1:35357/v3", "region": "RegionOne", "internalURL": "http://127.0.0.1:35357/v3", "publicURL": "http://127.0.0.1:5000/v3"}], "endpoints_links": [], "type": "identity", "name": "keystone"}], + "expires": "2012-06-02T14:47:34Z", + "project": {"enabled": true, "description": null, "name": "tenant_name1", "id": "tenant_id1", "domain": {"id": "domain_id1", "name": "domain_name1"}}, + "user": {"name": "user_name1", "id": "user_id1", "domain": {"id": "domain_id1", "name": "domain_name1"}}, + "roles": [{"name": "role1"}, {"name": "role2"}] + } +} diff --git a/examples/pki/cms/auth_v3_token_scoped.pem b/examples/pki/cms/auth_v3_token_scoped.pem new file mode 100644 index 0000000..e289ab1 --- /dev/null +++ b/examples/pki/cms/auth_v3_token_scoped.pem @@ -0,0 +1,42 @@ +-----BEGIN CMS----- +MIIHeAYJKoZIhvcNAQcCoIIHaTCCB2UCAQExCTAHBgUrDgMCGjCCBgYGCSqGSIb3 +DQEHAaCCBfcEggXzeyJ0b2tlbiI6IA0KCXsiY2F0YWxvZyI6IFt7ImVuZHBvaW50 +cyI6IFt7ImFkbWluVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3Ni92MS82NGI2 +ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsICJyZWdpb24iOiAicmVnaW9u +T25lIiwgImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3Ni92MS82 +NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsICJwdWJsaWNVUkwiOiAi +aHR0cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNm +ODliYjY2MTdhIn1dLCAiZW5kcG9pbnRzX2xpbmtzIjogW10sICJ0eXBlIjogInZv +bHVtZSIsICJuYW1lIjogInZvbHVtZSJ9LA0KCQkJICAgICB7ImVuZHBvaW50cyI6 +IFt7ImFkbWluVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIsICJyZWdp +b24iOiAicmVnaW9uT25lIiwgImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4w +LjE6OTI5Mi92MSIsICJwdWJsaWNVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo5Mjky +L3YxIn1dLCAiZW5kcG9pbnRzX2xpbmtzIjogW10sICJ0eXBlIjogImltYWdlIiwg +Im5hbWUiOiAiZ2xhbmNlIn0sDQoJCQkgICAgIHsiZW5kcG9pbnRzIjogW3siYWRt +aW5VUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUz +NDM1ZThhNjBmY2Y4OWJiNjYxN2EiLCAicmVnaW9uIjogInJlZ2lvbk9uZSIsICJp +bnRlcm5hbFVSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNm +YmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsICJwdWJsaWNVUkwiOiAiaHR0cDov +LzEyNy4wLjAuMTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJi +NjYxN2EifV0sICJlbmRwb2ludHNfbGlua3MiOiBbXSwgInR5cGUiOiAiY29tcHV0 +ZSIsICJuYW1lIjogIm5vdmEifSwNCgkJCSAgICAgeyJlbmRwb2ludHMiOiBbeyJh +ZG1pblVSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YzIiwgInJlZ2lvbiI6 +ICJSZWdpb25PbmUiLCAiaW50ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMToz +NTM1Ny92MyIsICJwdWJsaWNVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo1MDAwL3Yz +In1dLCAiZW5kcG9pbnRzX2xpbmtzIjogW10sICJ0eXBlIjogImlkZW50aXR5Iiwg +Im5hbWUiOiAia2V5c3RvbmUifV0sDQoJICJleHBpcmVzIjogIjIwMTItMDYtMDJU +MTQ6NDc6MzRaIiwNCgkgInByb2plY3QiOiB7ImVuYWJsZWQiOiB0cnVlLCAiZGVz +Y3JpcHRpb24iOiBudWxsLCAibmFtZSI6ICJ0ZW5hbnRfbmFtZTEiLCAiaWQiOiAi +dGVuYW50X2lkMSIsICJkb21haW4iOiB7ImlkIjogImRvbWFpbl9pZDEiLCAibmFt +ZSI6ICJkb21haW5fbmFtZTEifX0sDQoJICJ1c2VyIjogeyJuYW1lIjogInVzZXJf +bmFtZTEiLCAiaWQiOiAidXNlcl9pZDEiLCAiZG9tYWluIjogeyJpZCI6ICJkb21h +aW5faWQxIiwgIm5hbWUiOiAiZG9tYWluX25hbWUxIn19LA0KCSAicm9sZXMiOiBb +eyJuYW1lIjogInJvbGUxIn0sIHsibmFtZSI6ICJyb2xlMiJ9XQ0KCSB9DQp9DQox +ggFJMIIBRQIBATCBpDCBnjEKMAgGA1UEBRMBNTELMAkGA1UEBhMCVVMxCzAJBgNV +BAgTAkNBMRIwEAYDVQQHEwlTdW5ueXZhbGUxEjAQBgNVBAoTCU9wZW5TdGFjazER +MA8GA1UECxMIS2V5c3RvbmUxJTAjBgkqhkiG9w0BCQEWFmtleXN0b25lQG9wZW5z +dGFjay5vcmcxFDASBgNVBAMTC1NlbGYgU2lnbmVkAgERMAcGBSsOAwIaMA0GCSqG +SIb3DQEBAQUABIGAMyJ/o4F6kFPZJ1oGPOaJywv7WKia3x2IOxlDSGBOSfiH64MA +Im3kv3AUSfVd9S+ulTHHWST9XGD3eWx8dBMVYO/RcFk6+qala2ryrUYhlOWMkFsB +LCNl0HJoUElEPJuqrwVW7Uy90IE0oGbW5uxsm7qoGBHp1B5z2CikaJBKhgg= +-----END CMS----- diff --git a/examples/pki/cms/revocation_list.pem b/examples/pki/cms/revocation_list.pem index 969d10d..ff22443 100644 --- a/examples/pki/cms/revocation_list.pem +++ b/examples/pki/cms/revocation_list.pem @@ -6,7 +6,7 @@ MYIBSTCCAUUCAQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYD VQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3RhY2sx ETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVu c3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZAIBETAHBgUrDgMCGjANBgkq -hkiG9w0BAQEFAASBgDNDhvViAo8EqTVVvZ00pWUWjajTwoV1w1os1XDJ1XacBUo+ -rsh7gljIIVuvHL2F9C660I5jxhb7QVsTge3CwSiDmexxBAPOs4lNR5hFH7FdT47b -OK2qd0XnRjo5F7odUxIkozuQ/UISaNTPeWxGEMNVhpTXo2Dwn8wN1wrs/Z2E +hkiG9w0BAQEFAASBgEqJBkjT4owaIANEzzVTS17GG4VR/s1rQOAajqYCSt+PEsz4 +H1QVsstP/FznwrfrphEdAvosWs3vTx9GgDm1wI5gBeAP56rbtGqzsqZ1PrbzjRpI +5jHjMF99oMdVeazRCk4CaaoiFo9Rb7A4HfEGHAhoyOieW90Pz3PuLcQqLqSS -----END CMS----- diff --git a/examples/pki/gen_pki.sh b/examples/pki/gen_pki.sh index 9bf6c32..5cea13e 100755 --- a/examples/pki/gen_pki.sh +++ b/examples/pki/gen_pki.sh @@ -203,7 +203,7 @@ function check_openssl { } function gen_sample_cms { - for json_file in "${CMS_DIR}/auth_token_revoked.json" "${CMS_DIR}/auth_token_unscoped.json" "${CMS_DIR}/auth_token_scoped.json" "${CMS_DIR}/revocation_list.json" + for json_file in "${CMS_DIR}/auth_token_revoked.json" "${CMS_DIR}/auth_token_unscoped.json" "${CMS_DIR}/auth_token_scoped.json" "${CMS_DIR}/revocation_list.json" "${CMS_DIR}/auth_v3_token_scoped.json" "${CMS_DIR}/auth_v3_token_revoked.json" do openssl cms -sign -in $json_file -nosmimecap -signer $CERTS_DIR/signing_cert.pem -inkey $PRIVATE_DIR/signing_key.pem -outform PEM -nodetach -nocerts -noattr -out ${json_file/.json/.pem} done diff --git a/examples/pki/private/cakey.pem b/examples/pki/private/cakey.pem index 3db9c22..3513f92 100644 --- a/examples/pki/private/cakey.pem +++ b/examples/pki/private/cakey.pem @@ -1,16 +1,16 @@ -----BEGIN PRIVATE KEY----- -MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMXgnd5wlHApGxZ5 -8LrpEkHU995lT9PxtMgkp0tpFhg7R5HQw9K7TfQk5NHB28hNzf8UE/c0z2pJXggP -nAzvdx27NQeJGX5CWsi6fITZ8vH/+SxgfxxC+CE/6BkDpzw21MgBtq11vWL7XVax -NeU12Ax889U66i3CrObuCYt2mbpzAgMBAAECgYEAligxJE9CFSrcR14Zc3zSQeqe -fcFbpnXQveAyo2MHRTQWx2wobY19RjuI+DOn2IRSQbK2w+zrSLiMBonx3U8Kj8nx -A4EQ75GLJEEr81TvBoIZSJAqrowNrkXNq8W++qwjlGXRjKiBAYlKMrFvR4lij4XN -6cdB7kGdSIUmhvC20sECQQD4ebCGfsgFWnrqOrco6T9eQRTvP3+gJuqYXYLuVSTC -R4gHxT5QVXSZt/Hv3UWJ0BLDbyLzLGHf30w1AqgwsUP5AkEAy96qXq6j2+IRa5w7 -2G+KZHF5N/MK/Hyy27Jw67GBVeGQj1Dwq2ZGAJBZrfXjTtQQAGdQ7EfOTCAOzHgX -2Bx0ywJAYqfGbBBIkL+VEA0SDh9WNrE2g6u9m7P371kplEGgH7dRDmzFShYz/pin -aep8IrTHzmsBAHY9wiqh0mZkqzim2QJADTYdxkr89WfeByI1wp3f0wiDeXu3j4sp -MBGNPcjf/8fBTXhKUGEtUiYImbxggaA+dTg8x0MT/FzreJajvO6DJwJARMc6rhzv -aTlm4IgApcDPBeuz6SKex9TfvDUJpqACoFM4lMgyHADi9NrJBslxFHPP5eTiM2Ag -vI7EuW837e6raQ== +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOw4quFzQ/xbUOKu +LtXdiZLPA0Wi38iGEa+T8tp7j3US44wAamckdZb4cq9/Qx03EBKd2mcJvUoPrLnS +lnHQMH2VGA1whZpZTWqt8ydQdDYB1SUKeUoxcjq8EKl8X8Sd3dP5amlyFCOIGVhF +yAXYgaYlmf+s6FIzpY55Uy2zX+nZAgMBAAECgYEAkSGL03InHf/YpTzRJ7Kx2JH5 +d6pHBYNhkFc8yQFLNWnChfynYvFikbvZcnuk92kiJd34FoBEXSFeRNjed9SqRP+i +gBXy8nqDnnm6af/URHz1H00pbiTAS5xSJZ2XUFCAa0eJEdDv8bEWdTbhfbYc1Lt2 +FISQ1b0hO7gqI1cvoAUCQQD+RFOg1N6eaIiOowQL5YrT8+EywWZqDHAPAYpQvvdP +UxZtKA7lyiA8fy5bVGc3zmv6D3ZpNKPh5p4WpABvKC+DAkEA7dTaWrsJZr2V1plC +71JmexyQNJBrCQb3zoJo2oImuAVXPlj3aNhwJftPaZXt6brICBWfDH6CD/YH7rrt +6HyGcwJAAdrBuWSUExe0F0Y9G1EbSBx5QgODGbbpglKCjcA20Y9LlJQ8N5TX01ki +H2xoLFIHG5XNSUsm/tjNwmCD2Eu0vQJBAK1XVAaJB+MgDtOoRMbVUegs+1W0ZK7h +qz+SgQWxkrLRAbNpeHmsNqEYN9sG8a5G+oAZ8iBTHEyxzzpKeBfYms0CQB1EUSoS +I96Wh4Mae7TXak6aSfl/dF2c3vNB2oYjZTN58JM8l731bh2rI4/0kSPbV5Mtnmk4 +AOLVl+ZJjR6y90I= -----END PRIVATE KEY----- diff --git a/examples/pki/private/signing_key.pem b/examples/pki/private/signing_key.pem index b6ad710..2a21dbb 100644 --- a/examples/pki/private/signing_key.pem +++ b/examples/pki/private/signing_key.pem @@ -1,16 +1,16 @@ -----BEGIN PRIVATE KEY----- -MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALqEAuiAajMQufOO -XP2ZC7HXEG0x6iKWCRxJMBKMcnhI8IhhX4mZomfdaUhTXPTH43SZzfm3v53VUtfc -NZ66yb1f7nVbH+iWVpEz1+R8sZ5ZcM5wh6T+4H7zGcxrKs1pUnYg+pnEWwmEV/YQ -gmrht44QrSEiDQlWDGEBh+kEom/bAgMBAAECgYBywfSUHya4gqsW2toGQps6cauu -s85uN0glujY0w2tO7Pnpv5errvaI12cG1BvWlAIz5MohwlfIgc919wyavCyRJgQN -xQo5v5MEMYKKc8ppmXpRr03HLwoPLOHVs6UHRJQT9dhOBfmLzMZIP7P/lJlt2/1X -Okwxft/PWorczKX1aQJBAORlVqP+Cj4r5kz1A77agnCvINioV1VM5n9PvzPVzYLH -5r1I53RWFooy1Hx2RUCmtSRQMZMeI9iGMg9c8d3LJ4UCQQDRDuIAd3AoNBcwXKC4 -BPNkbI9BSqnpIdZo87BzpY8rJ/ra3VHMHuq4w+gQsmmEy3pp01AZd1uBqv3s1wHy -muffAkEAn2ZmiH+lUGy9B5q8qXfBL7naF7utb/gCqnnSvO+LxamUTSjTeKsYgg0l -pVO503xF0fkyEDYp2FUYHQbGOwAtLQJAHkJ3N/YRx9/yU0+0+63LxQdpnNu/yDzb -mglbywF1vZtl1fQe+NqowuGoX3JTj6McLuElQOpj1lr3siZU49bEJQJBANRazUzj -Xfoja7wGuZ3PwHdxxoNDlJ2u0rYjcfK9VZuPGSz/25iCOkaar3OralJ3lfCWbFKA -vvRp8Hl2Yk4hdKM= +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAKYm0YA4g5eLu96H +lUI5IjQTflWGEkPNlCaEL7qGG82J3QIpPT+t73+NpbtrUjLPFP6DZfxKh7yPdq2Y +Rqc6/cEVQELmimJaz5NUp/YSRxY46LUh2Eqvor5Ru1pi6CK0JGGMxa96/soomLcy +9HL+vKG4yhMBjGWrJ/eZyUo6gMRVAgMBAAECgYAmfB9Sn8R7ObaOWMFN0YYGoe1F +SgS5B8klEsErZxzRgvlaIss5EMTEur6EptsnQagPO8hHo8vE9UX796WF3rgfvYlm +rWzADFF9JQeb1CRy2wdPEB5wHYWksynKaRhPt6byv2qNqmTKB6JH3fbm1q7Hkrw6 +BjDvuadpdrWBzTPOEQJBANIdSkW2Yo0HVqZz428Ng1zXQQkwlONrFmtHV5OrPLKs +cu5qE2hGHlu3fxr/Gb/bqLwaCx+LUUjgEopChwQyQU8CQQDKb7vefl8JDRYNcLPM +CCT7D93g+kYW3ONziBYwQ0sOpSfIS1WQfNRVvsHFAb9IF9g+qgOo4rELEsDeKkUo +C88bAkAFHruZmUkrgJtG8RoAscaas5AdJjbql8hzEsj6iziube9bCfCxIMxKld0e +DktVVof1FXlh6mYvrW4mOlrJ6mOXAkAgCoFc3Pmj0BtucykyIRPhXQiMZHCVi87A +aYjBiNUnc0KRtELNxMRC8hdvXDBvc765ZGWB5KeLDiPSxjP9+6iDAkA+G1v4y4FP +r8jd6yPPORii1lTAnYAeoBvgqHj7l/2qi+QEYKIW7Q0pNx20BiO3YZoAgL7LoxyR +EVbW0VXOpHxk -----END PRIVATE KEY----- diff --git a/examples/pki/private/ssl_key.pem b/examples/pki/private/ssl_key.pem index 2e4667f..e8ad2d7 100644 --- a/examples/pki/private/ssl_key.pem +++ b/examples/pki/private/ssl_key.pem @@ -1,16 +1,16 @@ -----BEGIN PRIVATE KEY----- -MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALVu4bjaOH33yAx0 -WdpEqj4UDVsLxVjWxEpIbOlDlc6IfJd+cUriQtxf6ahjxtzLPERS81SnwZmrICWZ -ngbOn733pULMTZktTJH+o7C74NdKwUSNxjlCeWUy+FqIQoje4ygoJRPpMdkp1wHN -O0ZERwRN9e8M5TIlx/LRtk+q8bT5AgMBAAECgYAmwq6EYFJrTvE0//JmN/8qzfvg -dI5PoWpD+F8UInUxr2T2tHOdrOLd07vGVrKYXu7cJeCIOGKa4r02azAggioL/nE9 -FgPpqEC+QROvLuhFsk1gLZ2pGQ06sveKZVMH22h59BKZkYlhjh5qd4vlmhPqkmPp -gdXj7ZjDCJhhQdFVkQJBANp18k2mVksn8q29LMieVTSIZNN3ucDA1QHbim+3fp/O -GxCzU7Mv1Xfnu1zoRFu5/sF3YG0Zy3TGPDrEljBC3rUCQQDUnBjVFXL35OkBZqXW -taJPzGbsPoqAO+Ls2juS97zNzeGxUNhvcKuEvHO63PXqDxp1535DpvJEBN1rT2FF -iaO1AkEAt/QTWWFUTqrPxY6DNFdm5fpn9E1fg7icZJkKBDJeFJCH59MpCryfovzl -n0ERtq9ynlQ4RQYwdR8rvkylLvRP9QJAOiXHFOAc5XeR0nREfwiGL9TzgUFJl/DJ -C4ZULMnctVzNkTVPPItQHal87WppR26CCiUZ/161e6zo8eRv8hjG0QJABWqfYQuK -dWH8nxlXS+NFUDbsCdL+XpOVE7iEH7hvSw/A/kz40mLx8sDp/Fz1ysrogR/L+NGC -Vrlwm4q/WYJO0Q== +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAM323GVGJ6UImf6n +fz9P+9MURBo0okaV/3ewyfSMri8DbM0sCqDtC43R1jIrHtEdnUU7kHguFXc09p9p +HSRbblZ3TNUuZgfoLTNUUY5LETrXdlIQ8WQDqUZq2kSbUBWYkHOYlzmowoWa2hKU +C1ifHcleI2dVMW+LIkDhXPEc4XO1AgMBAAECgYBOZfMKkaOxjA6iAjvLa7Sdag9q +MjK6z4nIk4CsF4iN2K3ngyYgj1pgh0kTG5rFWpJssfmR5WjCUWS21RoEptDeZf/A +jRqzW3u493JAhyOjTK4DYbB9CwCmeGkoImC3nn2PrBgG1OPrSQMB3ODqVA2Pa1eF +omqKQmAqCCijtmllmQJBAOnpN3sjykUlGVWY7HxdBAOsQ5DkkCXL6ZSjA3pRYvJQ +12pKELZyxZ8GtVCFvOjaCpdxL+1MsRHkEfZpWz9o9BsCQQDhagjUFbgAQzo/TH1X +iblrnWUi7rs+IIDOF48qy/t1FKFlyCHbMYQLB/rPSN1G+5uMEapCuOBpVQsO9v5n +wJRvAkBQXOPG1sEDiH9vvR5ii8J5UJHWEfDES45wlqD3QUbxYXzg85lSVZQ30qIw +jAIfLeo9pZGFwbeEIgtZ0VCcNH7JAkBK3FEkRjY+eBUvEnMKEGYw9CuzZz9uCZNd +Xnughe/z2S8kw0tjJVWp6DOGhbdfLI5i/TbjQ8zbjm/Gv4aL5GwnAkEA42UWJKNQ +ztq73xmVmihToMjMe6k2DDPQpq+e2b/522Vz1ZDJlIV9tpoykFX2XiPnRz1o1oWd +DXQBvYeFzthvKA== -----END PRIVATE KEY----- diff --git a/keystoneclient/middleware/auth_token.py b/keystoneclient/middleware/auth_token.py index 5d1d33d..084d6f2 100644 --- a/keystoneclient/middleware/auth_token.py +++ b/keystoneclient/middleware/auth_token.py @@ -61,35 +61,69 @@ HTTP_X_IDENTITY_STATUS The underlying service will only see a value of 'Invalid' if the Middleware is configured to run in 'delay_auth_decision' mode -HTTP_X_TENANT_ID - Identity service managed unique identifier, string +HTTP_X_DOMAIN_ID + Identity service managed unique identifier, string. Only present if + this is a domain-scoped token. -HTTP_X_TENANT_NAME - Unique tenant identifier, string +HTTP_X_DOMAIN_NAME + Unique domain name, string. Only present if this is a domain-scoped token. + +HTTP_X_PROJECT_ID + Identity service managed unique identifier, string. Only present if + this is a project-scoped token. + +HTTP_X_PROJECT_NAME + Project name, unique within owning domain, string. Only present if + this is a project-scoped token. + +HTTP_X_PROJECT_DOMAIN_ID + Identity service managed unique identifier of owning domain of + project, string. Only present if this is a project-scoped token. + +HTTP_X_PROJECT_DOMAIN_NAME + Name of owning domain of project, string. Only present if this is a + project-scoped token. HTTP_X_USER_ID Identity-service managed unique identifier, string HTTP_X_USER_NAME - Unique user identifier, string + User identifier, unique within owning domain, string + +HTTP_X_USER_DOMAIN_ID + Identity service managed unique identifier of owning domain of user, string + +HTTP_X_USER_DOMAIN_NAME + Name of owning domain of user, string HTTP_X_ROLES - Comma delimited list of case-sensitive Roles + Comma delimited list of case-sensitive role names HTTP_X_SERVICE_CATALOG json encoded keystone service catalog (optional). +HTTP_X_TENANT_ID + *Deprecated* in favor of HTTP_X_PROJECT_ID + Identity service managed unique identifier, string. For v3 tokens, this + will be set to the same value as HTTP_X_PROJECT_ID + +HTTP_X_TENANT_NAME + *Deprecated* in favor of HTTP_X_PROJECT_NAME + Project identifier, unique within owning domain, string. For v3 tokens, + this will be set to the same value as HTTP_X_PROJECT_NAME + HTTP_X_TENANT *Deprecated* in favor of HTTP_X_TENANT_ID and HTTP_X_TENANT_NAME - Keystone-assigned unique identifier, deprecated + Keystone-assigned unique identifier, string. For v3 tokens, this + will be set to the same value as HTTP_X_PROJECT_ID HTTP_X_USER *Deprecated* in favor of HTTP_X_USER_ID and HTTP_X_USER_NAME - Unique user name, string + User name, unique within owning domain, string HTTP_X_ROLE *Deprecated* in favor of HTTP_X_ROLES - This is being renamed, and the new header contains the same data. + Will contain the same values as HTTP_X_ROLES. OTHER ENVIRONMENT VARIABLES --------------------------- @@ -157,8 +191,10 @@ opts = [ cfg.IntOpt('auth_port', default=35357), cfg.StrOpt('auth_protocol', default='https'), cfg.StrOpt('auth_uri', default=None), + cfg.StrOpt('auth_version', default=None), cfg.BoolOpt('delay_auth_decision', default=False), cfg.BoolOpt('http_connect_timeout', default=None), + cfg.StrOpt('http_handler', default=None), cfg.StrOpt('admin_token', secret=True), cfg.StrOpt('admin_user'), cfg.StrOpt('admin_password', secret=True), @@ -171,10 +207,12 @@ opts = [ cfg.ListOpt('memcache_servers'), cfg.IntOpt('token_cache_time', default=300), cfg.StrOpt('memcache_security_strategy', default=None), - cfg.StrOpt('memcache_secret_key', default=None, secret=True), + cfg.StrOpt('memcache_secret_key', default=None, secret=True) ] CONF.register_opts(opts, group='keystone_authtoken') +LIST_OF_VERSIONS_TO_ATTEMPT = ['v3.0', 'v2.0'] + def will_expire_soon(expiry): """ Determines if expiration is about to occur. @@ -221,10 +259,17 @@ class AuthProtocol(object): self.auth_host = self._conf_get('auth_host') self.auth_port = int(self._conf_get('auth_port')) self.auth_protocol = self._conf_get('auth_protocol') - if self.auth_protocol == 'http': - self.http_client_class = httplib.HTTPConnection + if not self._conf_get('http_handler'): + if self.auth_protocol == 'http': + self.http_client_class = httplib.HTTPConnection + else: + self.http_client_class = httplib.HTTPSConnection else: - self.http_client_class = httplib.HTTPSConnection + # Really only used for unit testing, since we need to + # have a fake handler set up before we issue an http + # request to get the list of versions supported by the + # server at the end of this initialization + self.http_client_class = self._conf_get('http_handler') self.auth_admin_prefix = self._conf_get('auth_admin_prefix') self.auth_uri = self._conf_get('auth_uri') @@ -289,6 +334,9 @@ class AuthProtocol(object): self.http_connect_timeout = (http_connect_timeout_cfg and int(http_connect_timeout_cfg)) + # Determine the highest api version we can use. + self.auth_version = self._choose_api_version() + def _assert_valid_memcache_protection_config(self): if self._memcache_security_strategy: if self._memcache_security_strategy not in ('MAC', 'ENCRYPT'): @@ -326,6 +374,60 @@ class AuthProtocol(object): else: return CONF.keystone_authtoken[name] + def _choose_api_version(self): + """ Determine the api version that we should use.""" + + # If the configuration specifies an auth_version we will just + # assume that is correct and use it. We could, of course, check + # that this version is supported by the server, but in case + # there are some problems in the field, we want as little code + # as possible in the way of letting auth_token talk to the + # server. + if self._conf_get('auth_version'): + version_to_use = self._conf_get('auth_version') + self.LOG.info('Auth Token proceeding with requested %s apis', + version_to_use) + else: + version_to_use = None + versions_supported_by_server = self._get_supported_versions() + if versions_supported_by_server: + for version in LIST_OF_VERSIONS_TO_ATTEMPT: + if version in versions_supported_by_server: + version_to_use = version + break + if version_to_use: + self.LOG.info('Auth Token confirmed use of %s apis', + version_to_use) + else: + self.LOG.error( + 'Attempted versions [%s] not in list supported by ' + 'server [%s]', + ', '.join(LIST_OF_VERSIONS_TO_ATTEMPT), + ', '.join(versions_supported_by_server)) + raise ServiceError('No compatible apis supported by server') + return version_to_use + + def _get_supported_versions(self): + versions = [] + response, data = self._json_request('GET', '/') + if response.status != 300: + self.LOG.error('Unable to get version info from keystone: %s' % + response.status) + raise ServiceError('Unable to get version info from keystone') + else: + try: + for version in data['versions']['values']: + versions.append(version['id']) + except KeyError: + self.LOG.error( + 'Invalid version response format from server', data) + raise ServiceError('Unable to parse version response ' + 'from keystone') + + self.LOG.debug('Server reports support for api versions: %s', + ', '.join(versions)) + return versions + def __call__(self, env, start_response): """Handle incoming request. @@ -371,14 +473,22 @@ class AuthProtocol(object): """ auth_headers = ( 'X-Identity-Status', - 'X-Tenant-Id', - 'X-Tenant-Name', + 'X-Domain-Id', + 'X-Domain-Name', + 'X-Project-Id', + 'X-Project-Name', + 'X-Project-Domain-Id', + 'X-Project-Domain-Name', 'X-User-Id', 'X-User-Name', + 'X-User-Domain-Id', + 'X-User-Domain-Name', 'X-Roles', 'X-Service-Catalog', # Deprecated 'X-User', + 'X-Tenant-Id', + 'X-Tenant-Name', 'X-Tenant', 'X-Role', ) @@ -459,7 +569,6 @@ class AuthProtocol(object): """ conn = self._get_http_connection() - try: conn.request(method, path) response = conn.getresponse() @@ -509,7 +618,6 @@ class AuthProtocol(object): raise ServiceError('Unable to communicate with keystone') finally: conn.close() - try: data = jsonutils.loads(body) except ValueError: @@ -524,6 +632,10 @@ class AuthProtocol(object): :return token id upon success :raises ServerError when unable to communicate with keystone + Irrespective of the auth version we are going to use for the + user token, for simplicity we always use a v2 admin token to + validate the user token. + """ params = { 'auth': { @@ -588,26 +700,35 @@ class AuthProtocol(object): Build headers that represent authenticated user: * X_IDENTITY_STATUS: Confirmed or Invalid - * X_TENANT_ID: id of tenant if tenant is present - * X_TENANT_NAME: name of tenant if tenant is present + * X_DOMAIN_ID: id of domain, if token is scoped to a domain + * X_DOMAIN_NAME: name of domain, if token is scoped to a domain + * X_PROJECT_ID: id of project, if token is scoped to a project + * X_PROJECT_NAME: name of project, if token is scoped to a project + * X_PROJECT_DOMAIN_ID: id of owning domain of project, if + token is scoped to a project + * X_PROJECT_DOMAIN_NAME: name of owning domain of project, if + token is scoped to a project * X_USER_ID: id of user * X_USER_NAME: name of user + * X_USER_DOMAIN_ID: id of owning domain of user + * X_USER_DOMAIN_NAME: name of owning domain of user * X_ROLES: list of roles * X_SERVICE_CATALOG: service catalog - Additional (deprecated) headers include: + Additional (deprecated) headers: * X_USER: name of user - * X_TENANT: For legacy compatibility before we had ID and Name + * X_TENANT_ID: id of tenant (which is equivilent to project), + if token is scoped to a project + * X_TENANT_NAME: name of tenant (which is equivilent to project), + if token is scoped to a project + * X_TENANT: For legacy compatibility before we had ID and Name, this + is will be the same as X_TENANT_NAME * X_ROLE: list of roles :param token_info: token object returned by keystone on authentication :raise InvalidUserToken when unable to parse token object """ - user = token_info['access']['user'] - token = token_info['access']['token'] - roles = ','.join([role['name'] for role in user.get('roles', [])]) - def get_tenant_info(): """Returns a (tenant_id, tenant_name) tuple from context.""" def essex(): @@ -619,7 +740,7 @@ class AuthProtocol(object): return (token['tenantId'], token['tenantId']) def default_tenant(): - """Assume the user's default tenant.""" + """Pre-grizzly, assume the user's default tenant.""" return (user['tenantId'], user['tenantName']) for method in [essex, pre_diablo, default_tenant]: @@ -630,26 +751,72 @@ class AuthProtocol(object): raise InvalidUserToken('Unable to determine tenancy.') - tenant_id, tenant_name = get_tenant_info() + # For clarity. set all those attributes that are optional in + # either a v2 or v3 token to None first + domain_id = None + domain_name = None + project_id = None + project_name = None + user_domain_id = None + user_domain_name = None + project_domain_id = None + project_domain_name = None + + if 'access' in token_info: + #v2 token + user = token_info['access']['user'] + token = token_info['access']['token'] + roles = ','.join([role['name'] for role in user.get('roles', [])]) + catalog_root = token_info['access'] + catalog_key = 'serviceCatalog' + project_id, project_name = get_tenant_info() + else: + #v3 token + token = token_info['token'] + user = token['user'] + user_domain_id = user['domain']['id'] + user_domain_name = user['domain']['name'] + roles = (','.join([role['name'] + for role in token.get('roles', [])])) + catalog_root = token + catalog_key = 'catalog' + # For v3, the server will put in the default project if there is + # one, so no need for us to add it here (like we do for a v2 token) + if 'domain' in token: + domain_id = token['domain']['id'] + domain_name = token['domain']['name'] + elif 'project' in token: + project_id = token['project']['id'] + project_name = token['project']['name'] + project_domain_id = token['project']['domain']['id'] + project_domain_name = token['project']['domain']['name'] user_id = user['id'] user_name = user['name'] rval = { 'X-Identity-Status': 'Confirmed', - 'X-Tenant-Id': tenant_id, - 'X-Tenant-Name': tenant_name, + 'X-Domain-Id': domain_id, + 'X-Domain-Name': domain_name, + 'X-Project-Id': project_id, + 'X-Project-Name': project_name, + 'X-Project-Domain-Id': project_domain_id, + 'X-Project-Domain-Name': project_domain_name, 'X-User-Id': user_id, 'X-User-Name': user_name, + 'X-User-Domain-Id': user_domain_id, + 'X-User-Domain-Name': user_domain_name, 'X-Roles': roles, # Deprecated 'X-User': user_name, - 'X-Tenant': tenant_name, + 'X-Tenant-Id': project_id, + 'X-Tenant-Name': project_name, + 'X-Tenant': project_name, 'X-Role': roles, } try: - catalog = token_info['access']['serviceCatalog'] + catalog = catalog_root[catalog_key] rval['X-Service-Catalog'] = jsonutils.dumps(catalog) except KeyError: pass @@ -781,11 +948,15 @@ class AuthProtocol(object): """ if self._cache and data: if 'token' in data.get('access', {}): + # It's a v2 token timestamp = data['access']['token']['expires'] - expires = timeutils.parse_isotime(timestamp).strftime('%s') + elif 'token' in data: + # It's a v3 token + timestamp = data['token']['expires'] else: self.LOG.error('invalid token format') return + expires = timeutils.parse_isotime(timestamp).strftime('%s') self.LOG.debug('Storing %s token in memcache', token) self._cache_store(token, data, expires) @@ -811,12 +982,19 @@ class AuthProtocol(object): :raise ServiceError if unable to authenticate token """ - - headers = {'X-Auth-Token': self.get_admin_token()} - response, data = self._json_request( - 'GET', - '/v2.0/tokens/%s' % safe_quote(user_token), - additional_headers=headers) + if self.auth_version == 'v3.0': + headers = {'X-Auth-Token': self.get_admin_token(), + 'X-Subject-Token': safe_quote(user_token)} + response, data = self._json_request( + 'GET', + '/v3/auth/tokens', + additional_headers=headers) + else: + headers = {'X-Auth-Token': self.get_admin_token()} + response, data = self._json_request( + 'GET', + '/v2.0/tokens/%s' % safe_quote(user_token), + additional_headers=headers) if response.status == 200: self._cache_put(user_token, data) @@ -910,6 +1088,7 @@ class AuthProtocol(object): timeout = (self.token_revocation_list_fetched_time + self.token_revocation_list_cache_timeout) list_is_current = timeutils.utcnow() < timeout + if list_is_current: # Load the list from disk if required if not self._token_revocation_list: diff --git a/tests/test_auth_token_middleware.py b/tests/test_auth_token_middleware.py index 58be41d..cc730a7 100644 --- a/tests/test_auth_token_middleware.py +++ b/tests/test_auth_token_middleware.py @@ -43,11 +43,16 @@ CA = os.path.join(CERTDIR, 'ca.pem') REVOCATION_LIST = None REVOKED_TOKEN = None REVOKED_TOKEN_HASH = None +REVOKED_v3_TOKEN = None +REVOKED_v3_TOKEN_HASH = None SIGNED_REVOCATION_LIST = None SIGNED_TOKEN_SCOPED = None SIGNED_TOKEN_UNSCOPED = None +SIGNED_v3_TOKEN_SCOPED = None +SIGNED_v3_TOKEN_UNSCOPED = None SIGNED_TOKEN_SCOPED_KEY = None SIGNED_TOKEN_UNSCOPED_KEY = None +SIGNED_v3_TOKEN_SCOPED_KEY = None VALID_SIGNED_REVOCATION_LIST = None @@ -55,6 +60,9 @@ UUID_TOKEN_DEFAULT = "ec6c0710ec2f471498484c1b53ab4f9d" UUID_TOKEN_NO_SERVICE_CATALOG = '8286720fbe4941e69fa8241723bb02df' UUID_TOKEN_UNSCOPED = '731f903721c14827be7b2dc912af7776' VALID_DIABLO_TOKEN = 'b0cf19b55dbb4f20a6ee18e6c6cf1726' +v3_UUID_TOKEN_DEFAULT = '5603457654b346fdbb93437bfe76f2f1' +v3_UUID_TOKEN_UNSCOPED = 'd34835fdaec447e695a0a024d84f8d79' +v3_UUID_TOKEN_DOMAIN_SCOPED = 'e8a7b63aaa4449f38f0c5c05c3581792' INVALID_SIGNED_TOKEN = string.replace( """AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA @@ -152,6 +160,79 @@ TOKEN_RESPONSES = { } }, }, + v3_UUID_TOKEN_DEFAULT: { + 'token': { + 'expires': '2999-01-01T00:00:10Z', + 'user': { + 'id': 'user_id1', + 'name': 'user_name1', + 'domain': { + 'id': 'domain_id1', + 'name': 'domain_name1' + } + }, + 'project': { + 'id': 'tenant_id1', + 'name': 'tenant_name1', + 'domain': { + 'id': 'domain_id1', + 'name': 'domain_name1' + } + }, + 'roles': [ + {'name': 'role1', 'id': 'Role1'}, + {'name': 'role2', 'id': 'Role2'}, + ], + 'catalog': {} + } + }, + v3_UUID_TOKEN_UNSCOPED: { + 'token': { + 'expires': '2999-01-01T00:00:10Z', + 'user': { + 'id': 'user_id1', + 'name': 'user_name1', + 'domain': { + 'id': 'domain_id1', + 'name': 'domain_name1' + } + } + } + }, + v3_UUID_TOKEN_DOMAIN_SCOPED: { + 'token': { + 'expires': '2999-01-01T00:00:10Z', + 'user': { + 'id': 'user_id1', + 'name': 'user_name1', + 'domain': { + 'id': 'domain_id1', + 'name': 'domain_name1' + } + }, + 'domain': { + 'id': 'domain_id1', + 'name': 'domain_name1', + }, + 'roles': [ + {'name': 'role1', 'id': 'Role1'}, + {'name': 'role2', 'id': 'Role2'}, + ], + 'catalog': {} + } + } +} + +EXPECTED_V2_DEFAULT_ENV_RESPONSE = { + 'HTTP_X_IDENTITY_STATUS': 'Confirmed', + 'HTTP_X_TENANT_ID': 'tenant_id1', + 'HTTP_X_TENANT_NAME': 'tenant_name1', + 'HTTP_X_USER_ID': 'user_id1', + 'HTTP_X_USER_NAME': 'user_name1', + 'HTTP_X_ROLES': 'role1,role2', + 'HTTP_X_USER': 'user_name1', # deprecated (diablo-compat) + 'HTTP_X_TENANT': 'tenant_name1', # deprecated (diablo-compat) + 'HTTP_X_ROLE': 'role1,role2', # deprecated (diablo-compat) } FAKE_RESPONSE_STACK = [] @@ -166,18 +247,25 @@ def setUpModule(self): self.SIGNED_TOKEN_SCOPED = cms.cms_to_token(f.read()) with open(os.path.join(signing_path, 'auth_token_unscoped.pem')) as f: self.SIGNED_TOKEN_UNSCOPED = cms.cms_to_token(f.read()) + with open(os.path.join(signing_path, 'auth_v3_token_scoped.pem')) as f: + self.SIGNED_v3_TOKEN_SCOPED = cms.cms_to_token(f.read()) with open(os.path.join(signing_path, 'auth_token_revoked.pem')) as f: self.REVOKED_TOKEN = cms.cms_to_token(f.read()) self.REVOKED_TOKEN_HASH = utils.hash_signed_token(self.REVOKED_TOKEN) + with open(os.path.join(signing_path, 'auth_v3_token_revoked.pem')) as f: + self.REVOKED_v3_TOKEN = cms.cms_to_token(f.read()) + self.REVOKED_v3_TOKEN_HASH = utils.hash_signed_token(self.REVOKED_v3_TOKEN) with open(os.path.join(signing_path, 'revocation_list.json')) as f: self.REVOCATION_LIST = jsonutils.loads(f.read()) with open(os.path.join(signing_path, 'revocation_list.pem')) as f: self.VALID_SIGNED_REVOCATION_LIST = jsonutils.dumps( {'signed': f.read()}) - self.SIGNED_TOKEN_SCOPED_KEY =\ - cms.cms_hash_token(self.SIGNED_TOKEN_SCOPED) - self.SIGNED_TOKEN_UNSCOPED_KEY =\ - cms.cms_hash_token(self.SIGNED_TOKEN_UNSCOPED) + self.SIGNED_TOKEN_SCOPED_KEY = ( + cms.cms_hash_token(self.SIGNED_TOKEN_SCOPED)) + self.SIGNED_TOKEN_UNSCOPED_KEY = ( + cms.cms_hash_token(self.SIGNED_TOKEN_UNSCOPED)) + self.SIGNED_v3_TOKEN_SCOPED_KEY = ( + cms.cms_hash_token(self.SIGNED_v3_TOKEN_SCOPED)) self.TOKEN_RESPONSES[self.SIGNED_TOKEN_SCOPED_KEY] = { 'access': { @@ -213,23 +301,80 @@ def setUpModule(self): }, }, + self.TOKEN_RESPONSES[self.SIGNED_v3_TOKEN_SCOPED_KEY] = { + 'token': { + 'expires': '2999-01-01T00:00:10Z', + 'user': { + 'id': 'user_id1', + 'name': 'user_name1', + 'domain': { + 'id': 'domain_id1', + 'name': 'domain_name1' + } + }, + 'project': { + 'id': 'tenant_id1', + 'name': 'tenant_name1', + 'domain': { + 'id': 'domain_id1', + 'name': 'domain_name1' + } + }, + 'roles': [ + {'name': 'role1'}, + {'name': 'role2'} + ], + 'catalog': {} + } + } + +VERSION_LIST_v3 = { + "versions": { + "values": [ + { + "id": "v3.0", + "status": "stable", + "updated": "2013-03-06T00:00:00Z", + "links": [] + }, + { + "id": "v2.0", + "status": "beta", + "updated": "2011-11-19T00:00:00Z", + "links": [] + } + ] + } +} + +VERSION_LIST_v2 = { + "versions": { + "values": [ + { + "id": "v2.0", + "status": "beta", + "updated": "2011-11-19T00:00:00Z", + "links": [] + } + ] + } +} + class FakeMemcache(object): def __init__(self): self.set_key = None self.set_value = None self.token_expiration = None + self.token_key = SIGNED_TOKEN_SCOPED_KEY def get(self, key): - data = TOKEN_RESPONSES[SIGNED_TOKEN_SCOPED_KEY].copy() - if not data or key != "tokens/%s" % (data['access']['token']['id']): + data = TOKEN_RESPONSES[self.token_key].copy() + if not data or key != "tokens/%s" % self.token_key: return if not self.token_expiration: dt = datetime.datetime.now() + datetime.timedelta(minutes=5) self.token_expiration = dt.strftime("%s") - dt = datetime.datetime.now() + datetime.timedelta(hours=24) - ks_expires = dt.isoformat() - data['access']['token']['expires'] = ks_expires return (data, str(self.token_expiration)) def set(self, key, value, time=None): @@ -237,22 +382,26 @@ class FakeMemcache(object): self.set_key = key +class v3FakeMemcache(FakeMemcache): + def __init__(self): + super(v3FakeMemcache, self).__init__() + self.token_key = SIGNED_v3_TOKEN_SCOPED_KEY + + class FakeSwiftMemcacheRing(object): def __init__(self): self.set_key = None self.set_value = None self.token_expiration = None + self.token_key = SIGNED_TOKEN_SCOPED_KEY def get(self, key): - data = TOKEN_RESPONSES[SIGNED_TOKEN_SCOPED_KEY].copy() - if not data or key != "tokens/%s" % (data['access']['token']['id']): + data = TOKEN_RESPONSES[self.token_key].copy() + if not data or key != "tokens/%s" % self.token_key: return if not self.token_expiration: dt = datetime.datetime.now() + datetime.timedelta(minutes=5) self.token_expiration = dt.strftime("%s") - dt = datetime.datetime.now() + datetime.timedelta(hours=24) - ks_expires = dt.isoformat() - data['access']['token']['expires'] = ks_expires return (data, str(self.token_expiration)) def set(self, key, value, serialize=True, timeout=0): @@ -260,6 +409,12 @@ class FakeSwiftMemcacheRing(object): self.set_key = key +class v3FakeSwiftMemcacheRing(FakeSwiftMemcacheRing): + def __init__(self): + super(v3FakeSwiftMemcacheRing, self).__init__() + self.token_key = SIGNED_v3_TOKEN_SCOPED_KEY + + class FakeHTTPResponse(object): def __init__(self, status, body): self.status = status @@ -269,86 +424,129 @@ class FakeHTTPResponse(object): return self.body -class FakeStackHTTPConnection(object): - - def __init__(self, *args, **kwargs): - pass +class BaseFakeHTTPConnection(object): - def getresponse(self): - if len(FAKE_RESPONSE_STACK): - return FAKE_RESPONSE_STACK.pop() - return FakeHTTPResponse(500, jsonutils.dumps('UNEXPECTED RESPONSE')) + def _user_token_responses(self, token_id): + """ Emulate user token responses. - def request(self, *_args, **_kwargs): - pass - - def close(self): - pass + Return success if the token is in the list we know + about. If the request is for revoked tokens, then return + the revoked list, else if a different token is provided, + return 404 indicating an unknown (therefore unauthorized) token. + """ + if token_id in TOKEN_RESPONSES.keys(): + status = 200 + body = jsonutils.dumps(TOKEN_RESPONSES[token_id]) + elif token_id == "revoked": + status = 200 + body = SIGNED_REVOCATION_LIST + else: + status = 404 + body = str() + return status, body + + def fake_v2_responses(self, path): + token_id = path.rsplit('/', 1)[1] + return self._user_token_responses(token_id) + + def fake_v3_responses(self, path, **kwargs): + headers = kwargs.get('headers') + token_id = headers['X-Subject-Token'] + return self._user_token_responses(token_id) + + def fake_v2_admin_token(self, path): + status = 200 + body = jsonutils.dumps({ + 'access': { + 'token': {'id': 'admin_token2', + 'expires': '2012-10-03T16:58:01Z'} + }, + }) + return status, body -class FakeHTTPConnection(object): - last_requested_url = '' +class FakeHTTPConnection(BaseFakeHTTPConnection): + """ Emulate a fake Keystone v2 server """ def __init__(self, *args, **kwargs): self.send_valid_revocation_list = True + self.resp = None def request(self, method, path, **kwargs): """Fakes out several http responses. - If a POST request is made, we assume the calling code is trying - to get a new admin token. + Support the following requests: - If a GET request is made to validate a token, return success - if the token is 'token1'. If a different token is provided, return - a 404, indicating an unknown (therefore unauthorized) token. + - Create admin token ('POST /testadmin/v2.0/tokens') + - Get versions ('GET /testadmin/') + - Get v2 user token responses (see fake_v2_responses) """ FakeHTTPConnection.last_requested_url = path - if method == 'POST': - status = 200 - body = jsonutils.dumps({ - 'access': { - 'token': {'id': 'admin_token2'}, - }, - }) - + if method == 'POST' and path == '/testadmin/v2.0/tokens': + status, body = self.fake_v2_admin_token(path) else: - token_id = path.rsplit('/', 1)[1] - if token_id in TOKEN_RESPONSES.keys(): - status = 200 - body = jsonutils.dumps(TOKEN_RESPONSES[token_id]) - elif token_id == "revoked": - status = 200 - body = SIGNED_REVOCATION_LIST + if path == '/testadmin/': + # It's a GET versions call + status = 300 + body = jsonutils.dumps(VERSION_LIST_v2) else: - status = 404 - body = str() + status, body = self.fake_v2_responses(path) self.resp = FakeHTTPResponse(status, body) def getresponse(self): - return self.resp + # If self.resp is set then this is just the response to + # the earlier request. If it is not set, then we expect + # a stack of responses to have been pre-prepared + if self.resp: + return self.resp + else: + if len(FAKE_RESPONSE_STACK): + return FAKE_RESPONSE_STACK.pop() + return FakeHTTPResponse( + 500, jsonutils.dumps('UNEXPECTED RESPONSE')) def close(self): pass +class v3FakeHTTPConnection(FakeHTTPConnection): + """ Emulate a fake Keystone v3 server """ + + def request(self, method, path, **kwargs): + """Fakes out several http responses. + + Support the following requests: + + - Create admin token ('POST /testadmin/v2.0/tokens') + - Get versions ('GET /testadmin/') + - Get v2 user token responses (see fake_v2_responses) + - Get v3 user token responses (see fake_v3_responses) + + """ + v3FakeHTTPConnection.last_requested_url = path + if method == 'POST' and path == '/testadmin/v2.0/tokens': + status, body = self.fake_v2_admin_token(path) + else: + if path == '/testadmin/': + # It's a GET versions call + status = 300 + body = jsonutils.dumps(VERSION_LIST_v3) + elif path.split('/')[2] == 'v2.0': + status, body = self.fake_v2_responses(path) + else: + status, body = self.fake_v3_responses(path, **kwargs) + + self.resp = FakeHTTPResponse(status, body) + + class FakeApp(object): """This represents a WSGI app protected by the auth_token middleware.""" def __init__(self, expected_env=None): expected_env = expected_env or {} - self.expected_env = { - 'HTTP_X_IDENTITY_STATUS': 'Confirmed', - 'HTTP_X_TENANT_ID': 'tenant_id1', - 'HTTP_X_TENANT_NAME': 'tenant_name1', - 'HTTP_X_USER_ID': 'user_id1', - 'HTTP_X_USER_NAME': 'user_name1', - 'HTTP_X_ROLES': 'role1,role2', - 'HTTP_X_USER': 'user_name1', # deprecated (diablo-compat) - 'HTTP_X_TENANT': 'tenant_name1', # deprecated (diablo-compat) - 'HTTP_X_ROLE': 'role1,role2', # deprecated (diablo-compat) - } + self.expected_env = dict(EXPECTED_V2_DEFAULT_ENV_RESPONSE) self.expected_env.update(expected_env) def __call__(self, env, start_response): @@ -360,36 +558,121 @@ class FakeApp(object): return resp(env, start_response) +class v3FakeApp(object): + """This represents a v3 WSGI app protected by the auth_token middleware.""" + def __init__(self, expected_env=None): + expected_env = expected_env or {} + # We should always get back the same v2 items + self.expected_env = dict(EXPECTED_V2_DEFAULT_ENV_RESPONSE) + # ...and with v3 additions, these are for the DEFAULT TOKEN + v3_default_env_additions = { + 'HTTP_X_PROJECT_ID': 'tenant_id1', + 'HTTP_X_PROJECT_NAME': 'tenant_name1', + 'HTTP_X_PROJECT_DOMAIN_ID': 'domain_id1', + 'HTTP_X_PROJECT_DOMAIN_NAME': 'domain_name1', + 'HTTP_X_USER_DOMAIN_ID': 'domain_id1', + 'HTTP_X_USER_DOMAIN_NAME': 'domain_name1' + } + self.expected_env.update(v3_default_env_additions) + # And finally update for anything passed in + self.expected_env.update(expected_env) + + def __call__(self, env, start_response): + for k, v in self.expected_env.items(): + assert env[k] == v, '%s != %s' % (env[k], v) + resp = webob.Response() + resp.body = 'SUCCESS' + return resp(env, start_response) + + class BaseAuthTokenMiddlewareTest(testtools.TestCase): + """ Base test class for auth_token middleware. + + All the tests allow for running with auth_token + configured for receiving v2 or v3 tokens, with the + choice being made by passing configuration data into + Setup(). - def setUp(self, expected_env=None): + The base class will, by default, run all the tests + expecting v2 token formats. Child classes can override + this to specify, for instance, v3 format. + + """ + def setUp(self, expected_env=None, auth_version=None, + fake_app=None, fake_http=None, token_dict=None, + fake_memcache=None, fake_memcache_ring=None): testtools.TestCase.setUp(self) expected_env = expected_env or {} - conf = { - 'admin_token': 'admin_token1', + if token_dict: + self.token_dict = token_dict + else: + self.token_dict = { + 'uuid_token_default': UUID_TOKEN_DEFAULT, + 'uuid_token_unscoped': UUID_TOKEN_UNSCOPED, + 'signed_token_scoped': SIGNED_TOKEN_SCOPED, + 'revoked_token': REVOKED_TOKEN, + 'revoked_token_hash': REVOKED_TOKEN_HASH + } + + self.conf = { 'auth_host': 'keystone.example.com', 'auth_port': 1234, 'auth_admin_prefix': '/testadmin', 'signing_dir': CERTDIR, + 'auth_version': auth_version } - self.middleware = auth_token.AuthProtocol(FakeApp(expected_env), conf) - self.middleware.http_client_class = FakeHTTPConnection - self.middleware._iso8601 = iso8601 + # Base assumes v2 for fake app and http, can be overridden for + # child classes by called set_middleware() directly + self.fake_app = fake_app or FakeApp + self.fake_http = fake_http or FakeHTTPConnection + self.set_middleware(self.fake_app, self.fake_http, + expected_env, self.conf) self.response_status = None self.response_headers = None + + self.fake_memcache = fake_memcache or FakeMemcache + self.fake_memcache_ring = fake_memcache_ring or FakeSwiftMemcacheRing + + signed_list = 'SIGNED_REVOCATION_LIST' + valid_signed_list = 'VALID_SIGNED_REVOCATION_LIST' + globals()[signed_list] = globals()[valid_signed_list] + + def set_fake_http(self, http_handler): + """ Configure the http handler for the auth_token middleware. + + Allows tests to override the default handler on specific tests, + e.g. to use v2 for those parts of auth_token that still use v2 + tokens while running the v3 test class, i.e. getting an admin + token or revocation list. + + """ + self.middleware.http_client_class = http_handler + + def set_middleware(self, fake_app=None, fake_http=None, + expected_env=None, conf=None): + """ Configure the class ready to call the auth_token middleware. + + Set up the various fake items needed to run the middleware. + Individual tests that need to further refine these can call this + function to override the class defaults. + + """ + conf = conf or self.conf + if 'http_handler' not in conf: + fake_http = fake_http or self.fake_http + conf['http_handler'] = fake_http + fake_app = fake_app or self.fake_app + self.middleware = auth_token.AuthProtocol(fake_app(expected_env), conf) + self.middleware._iso8601 = iso8601 self.middleware.revoked_file_name = tempfile.mkstemp()[1] cache_timeout = datetime.timedelta(days=1) self.middleware.token_revocation_list_cache_timeout = cache_timeout self.middleware.token_revocation_list = jsonutils.dumps( {"revoked": [], "extra": "success"}) - signed_list = 'SIGNED_REVOCATION_LIST' - valid_signed_list = 'VALID_SIGNED_REVOCATION_LIST' - globals()[signed_list] = globals()[valid_signed_list] - def tearDown(self): testtools.TestCase.tearDown(self) try: @@ -449,9 +732,8 @@ class StackResponseAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest): """ - def setUp(self, expected_env=None): - super(StackResponseAuthTokenMiddlewareTest, self).setUp(expected_env) - self.middleware.http_client_class = FakeStackHTTPConnection + def setUp(self): + super(StackResponseAuthTokenMiddlewareTest, self).setUp() def test_fetch_revocation_list_with_expire(self): # first response to revocation list should return 401 Unauthorized @@ -493,7 +775,8 @@ class DiabloAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest): # now deprecated (diablo-compat) 'HTTP_X_TENANT': 'tenant_id1', } - super(DiabloAuthTokenMiddlewareTest, self).setUp(expected_env) + super(DiabloAuthTokenMiddlewareTest, self).setUp( + expected_env=expected_env) def test_valid_diablo_response(self): req = webob.Request.blank('/') @@ -504,72 +787,47 @@ class DiabloAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest): class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest): - def assert_valid_request_200(self, token): + + def assert_valid_last_url(self, token_id): + # Default version (v2) has id in the token, override this + # method for v3 and other versions + self.assertEqual("/testadmin/v2.0/tokens/%s" % token_id, + self.middleware.http_client_class.last_requested_url) + + def assert_valid_request_200(self, token, with_catalog=True): req = webob.Request.blank('/') req.headers['X-Auth-Token'] = token body = self.middleware(req.environ, self.start_fake_response) self.assertEqual(self.response_status, 200) - self.assertTrue(req.headers.get('X-Service-Catalog')) + if with_catalog: + self.assertTrue(req.headers.get('X-Service-Catalog')) self.assertEqual(body, ['SUCCESS']) self.assertTrue('keystone.token_info' in req.environ) def test_valid_uuid_request(self): - self.assert_valid_request_200(UUID_TOKEN_DEFAULT) - self.assertEqual("/testadmin/v2.0/tokens/%s" % UUID_TOKEN_DEFAULT, - FakeHTTPConnection.last_requested_url) + self.assert_valid_request_200(self.token_dict['uuid_token_default']) + self.assert_valid_last_url(self.token_dict['uuid_token_default']) def test_valid_signed_request(self): - FakeHTTPConnection.last_requested_url = '' - self.assert_valid_request_200(SIGNED_TOKEN_SCOPED) + self.middleware.http_client_class.last_requested_url = '' + self.assert_valid_request_200( + self.token_dict['signed_token_scoped']) self.assertEqual(self.middleware.conf['auth_admin_prefix'], "/testadmin") #ensure that signed requests do not generate HTTP traffic - self.assertEqual('', FakeHTTPConnection.last_requested_url) - - def assert_unscoped_default_tenant_auto_scopes(self, token): - """Unscoped requests with a default tenant should "auto-scope." - - The implied scope is the user's tenant ID. - - """ - req = webob.Request.blank('/') - req.headers['X-Auth-Token'] = token - body = self.middleware(req.environ, self.start_fake_response) - self.assertEqual(self.response_status, 200) - self.assertEqual(body, ['SUCCESS']) - self.assertTrue('keystone.token_info' in req.environ) - - def test_default_tenant_uuid_token(self): - self.assert_unscoped_default_tenant_auto_scopes(UUID_TOKEN_DEFAULT) - - def test_default_tenant_signed_token(self): - self.assert_unscoped_default_tenant_auto_scopes(SIGNED_TOKEN_SCOPED) - - def assert_unscoped_token_receives_401(self, token): - """Unscoped requests with no default tenant ID should be rejected.""" - req = webob.Request.blank('/') - req.headers['X-Auth-Token'] = token - self.middleware(req.environ, self.start_fake_response) - self.assertEqual(self.response_status, 401) - self.assertEqual(self.response_headers['WWW-Authenticate'], - 'Keystone uri=\'https://keystone.example.com:1234\'') - - def test_unscoped_uuid_token_receives_401(self): - self.assert_unscoped_token_receives_401(UUID_TOKEN_UNSCOPED) - - def test_unscoped_pki_token_receives_401(self): - self.assert_unscoped_token_receives_401(SIGNED_TOKEN_UNSCOPED) + self.assertEqual( + '', self.middleware.http_client_class.last_requested_url) def test_revoked_token_receives_401(self): self.middleware.token_revocation_list = self.get_revocation_list_json() req = webob.Request.blank('/') - req.headers['X-Auth-Token'] = REVOKED_TOKEN + req.headers['X-Auth-Token'] = self.token_dict['revoked_token'] self.middleware(req.environ, self.start_fake_response) self.assertEqual(self.response_status, 401) def get_revocation_list_json(self, token_ids=None): if token_ids is None: - token_ids = [REVOKED_TOKEN_HASH] + token_ids = [self.token_dict['revoked_token_hash']] revocation_list = {'revoked': [{'id': x, 'expires': timeutils.utcnow()} for x in token_ids]} return jsonutils.dumps(revocation_list) @@ -578,22 +836,26 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest): #explicitly setting an empty revocation list here to document intent self.middleware.token_revocation_list = jsonutils.dumps( {"revoked": [], "extra": "success"}) - result = self.middleware.is_signed_token_revoked(REVOKED_TOKEN) + result = self.middleware.is_signed_token_revoked( + self.token_dict['revoked_token']) self.assertFalse(result) def test_is_signed_token_revoked_returns_true(self): self.middleware.token_revocation_list = self.get_revocation_list_json() - result = self.middleware.is_signed_token_revoked(REVOKED_TOKEN) + result = self.middleware.is_signed_token_revoked( + self.token_dict['revoked_token']) self.assertTrue(result) def test_verify_signed_token_raises_exception_for_revoked_token(self): self.middleware.token_revocation_list = self.get_revocation_list_json() self.assertRaises(auth_token.InvalidUserToken, - self.middleware.verify_signed_token, REVOKED_TOKEN) + self.middleware.verify_signed_token, + self.token_dict['revoked_token']) def test_verify_signed_token_succeeds_for_unrevoked_token(self): self.middleware.token_revocation_list = self.get_revocation_list_json() - self.middleware.verify_signed_token(SIGNED_TOKEN_SCOPED) + self.middleware.verify_signed_token( + self.token_dict['signed_token_scoped']) def test_cert_file_missing(self): self.assertFalse(self.middleware.cert_file_missing( @@ -622,6 +884,9 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest): expected) def test_get_revocation_list_returns_fetched_list(self): + # auth_token uses v2 to fetch this, so don't allow the v3 + # tests to override the fake http connection + self.set_fake_http(FakeHTTPConnection) self.middleware.token_revocation_list_fetched_time = None os.remove(self.middleware.revoked_file_name) self.assertEqual(self.middleware.token_revocation_list, @@ -642,6 +907,9 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest): self.middleware.fetch_revocation_list) def test_fetch_revocation_list(self): + # auth_token uses v2 to fetch this, so don't allow the v3 + # tests to override the fake http connection + self.set_fake_http(FakeHTTPConnection) fetched_list = jsonutils.loads(self.middleware.fetch_revocation_list()) self.assertEqual(fetched_list, REVOCATION_LIST) @@ -651,7 +919,7 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest): self.middleware(req.environ, self.start_fake_response) self.assertEqual(self.response_status, 401) self.assertEqual(self.response_headers['WWW-Authenticate'], - 'Keystone uri=\'https://keystone.example.com:1234\'') + "Keystone uri='https://keystone.example.com:1234'") def test_request_invalid_signed_token(self): req = webob.Request.blank('/') @@ -659,14 +927,14 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest): self.middleware(req.environ, self.start_fake_response) self.assertEqual(self.response_status, 401) self.assertEqual(self.response_headers['WWW-Authenticate'], - 'Keystone uri=\'https://keystone.example.com:1234\'') + "Keystone uri='https://keystone.example.com:1234'") def test_request_no_token(self): req = webob.Request.blank('/') self.middleware(req.environ, self.start_fake_response) self.assertEqual(self.response_status, 401) self.assertEqual(self.response_headers['WWW-Authenticate'], - 'Keystone uri=\'https://keystone.example.com:1234\'') + "Keystone uri='https://keystone.example.com:1234'") def test_request_no_token_log_message(self): class FakeLog(object): @@ -693,12 +961,13 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest): self.middleware(req.environ, self.start_fake_response) self.assertEqual(self.response_status, 401) self.assertEqual(self.response_headers['WWW-Authenticate'], - 'Keystone uri=\'https://keystone.example.com:1234\'') + "Keystone uri='https://keystone.example.com:1234'") def test_memcache(self): req = webob.Request.blank('/') - req.headers['X-Auth-Token'] = SIGNED_TOKEN_SCOPED - self.middleware._cache = FakeMemcache() + req.headers['X-Auth-Token'] = ( + self.token_dict['signed_token_scoped']) + self.middleware._cache = self.fake_memcache() self.middleware._use_keystone_cache = True self.middleware(req.environ, self.start_fake_response) self.assertEqual(self.middleware._cache.set_value, None) @@ -706,15 +975,16 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest): def test_memcache_set_invalid(self): req = webob.Request.blank('/') req.headers['X-Auth-Token'] = 'invalid-token' - self.middleware._cache = FakeMemcache() + self.middleware._cache = self.fake_memcache() self.middleware._use_keystone_cache = True self.middleware(req.environ, self.start_fake_response) self.assertEqual(self.middleware._cache.set_value, "invalid") def test_memcache_set_expired(self): req = webob.Request.blank('/') - req.headers['X-Auth-Token'] = SIGNED_TOKEN_SCOPED - self.middleware._cache = FakeMemcache() + req.headers['X-Auth-Token'] = ( + self.token_dict['signed_token_scoped']) + self.middleware._cache = self.fake_memcache() self.middleware._use_keystone_cache = True expired = datetime.datetime.now() - datetime.timedelta(minutes=1) self.middleware._cache.token_expiration = float(expired.strftime("%s")) @@ -723,8 +993,9 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest): def test_swift_memcache_set_expired(self): req = webob.Request.blank('/') - req.headers['X-Auth-Token'] = SIGNED_TOKEN_SCOPED - self.middleware._cache = FakeSwiftMemcacheRing() + req.headers['X-Auth-Token'] = ( + self.token_dict['signed_token_scoped']) + self.middleware._cache = self.fake_memcache_ring() self.middleware._use_keystone_cache = False expired = datetime.datetime.now() - datetime.timedelta(minutes=1) self.middleware._cache.token_expiration = float(expired.strftime("%s")) @@ -735,48 +1006,38 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest): self.disable_module('memcache') conf = { - 'admin_token': 'admin_token1', 'auth_host': 'keystone.example.com', 'auth_port': 1234, - 'memcache_servers': 'localhost:11211', + 'auth_admin_prefix': '/testadmin', + 'memcache_servers': 'localhost:11211' } - - auth_token.AuthProtocol(FakeApp(), conf) + self.set_middleware(conf=conf) def test_use_cache_from_env(self): env = {'swift.cache': 'CACHE_TEST'} conf = { - 'admin_token': 'admin_token1', 'auth_host': 'keystone.example.com', 'auth_port': 1234, + 'auth_admin_prefix': '/testadmin', 'cache': 'swift.cache', - 'memcache_servers': 'localhost:11211', + 'memcache_servers': 'localhost:11211' } - auth = auth_token.AuthProtocol(FakeApp(), conf) - auth._init_cache(env) - self.assertEqual(auth._cache, 'CACHE_TEST') + self.set_middleware(conf=conf) + self.middleware._init_cache(env) + self.assertEqual(self.middleware._cache, 'CACHE_TEST') def test_not_use_cache_from_env(self): self.disable_module('memcache') env = {'swift.cache': 'CACHE_TEST'} conf = { - 'admin_token': 'admin_token1', 'auth_host': 'keystone.example.com', 'auth_port': 1234, - 'memcache_servers': 'localhost:11211', + 'auth_admin_prefix': '/testadmin', + 'memcache_servers': 'localhost:11211' } - auth = auth_token.AuthProtocol(FakeApp(), conf) - auth._init_cache(env) - self.assertEqual(auth._cache, None) - - def test_request_prevent_service_catalog_injection(self): - req = webob.Request.blank('/') - req.headers['X-Service-Catalog'] = '[]' - req.headers['X-Auth-Token'] = UUID_TOKEN_NO_SERVICE_CATALOG - body = self.middleware(req.environ, self.start_fake_response) - self.assertEqual(self.response_status, 200) - self.assertFalse(req.headers.get('X-Service-Catalog')) - self.assertEqual(body, ['SUCCESS']) + self.set_middleware(conf=conf) + self.middleware._init_cache(env) + self.assertEqual(self.middleware._cache, None) def test_will_expire_soon(self): tenseconds = datetime.datetime.utcnow() + datetime.timedelta( @@ -788,152 +1049,328 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest): def test_encrypt_cache_data(self): conf = { - 'admin_token': 'admin_token1', 'auth_host': 'keystone.example.com', 'auth_port': 1234, + 'auth_admin_prefix': '/testadmin', 'memcache_servers': 'localhost:11211', 'memcache_security_strategy': 'encrypt', - 'memcache_secret_key': 'mysecret', + 'memcache_secret_key': 'mysecret' } - auth = auth_token.AuthProtocol(FakeApp(), conf) - encrypted_data = \ - auth._protect_cache_value('token', - TOKEN_RESPONSES[UUID_TOKEN_DEFAULT]) + self.set_middleware(conf=conf) + encrypted_data = self.middleware._protect_cache_value( + 'token', TOKEN_RESPONSES[self.token_dict['uuid_token_default']]) self.assertEqual('{ENCRYPT:AES256}', encrypted_data[:16]) self.assertEqual( - TOKEN_RESPONSES[UUID_TOKEN_DEFAULT], - auth._unprotect_cache_value('token', encrypted_data)) + TOKEN_RESPONSES[self.token_dict['uuid_token_default']], + self.middleware._unprotect_cache_value('token', encrypted_data)) # should return None if unable to decrypt self.assertIsNone( - auth._unprotect_cache_value('token', '{ENCRYPT:AES256}corrupted')) + self.middleware._unprotect_cache_value( + 'token', '{ENCRYPT:AES256}corrupted')) self.assertIsNone( - auth._unprotect_cache_value('mykey', encrypted_data)) + self.middleware._unprotect_cache_value('mykey', encrypted_data)) def test_sign_cache_data(self): conf = { - 'admin_token': 'admin_token1', 'auth_host': 'keystone.example.com', 'auth_port': 1234, + 'auth_admin_prefix': '/testadmin', 'memcache_servers': 'localhost:11211', 'memcache_security_strategy': 'mac', - 'memcache_secret_key': 'mysecret', + 'memcache_secret_key': 'mysecret' } - auth = auth_token.AuthProtocol(FakeApp(), conf) - signed_data = \ - auth._protect_cache_value('mykey', - TOKEN_RESPONSES[UUID_TOKEN_DEFAULT]) + self.set_middleware(conf=conf) + signed_data = self.middleware._protect_cache_value( + 'mykey', TOKEN_RESPONSES[self.token_dict['uuid_token_default']]) expected = '{MAC:SHA1}' self.assertEqual( signed_data[:10], expected) self.assertEqual( - TOKEN_RESPONSES[UUID_TOKEN_DEFAULT], - auth._unprotect_cache_value('mykey', signed_data)) + TOKEN_RESPONSES[self.token_dict['uuid_token_default']], + self.middleware._unprotect_cache_value('mykey', signed_data)) # should return None on corrupted data self.assertIsNone( - auth._unprotect_cache_value('mykey', '{MAC:SHA1}corrupted')) + self.middleware._unprotect_cache_value('mykey', + '{MAC:SHA1}corrupted')) def test_no_memcache_protection(self): conf = { - 'admin_token': 'admin_token1', 'auth_host': 'keystone.example.com', 'auth_port': 1234, + 'auth_admin_prefix': '/testadmin', 'memcache_servers': 'localhost:11211', - 'memcache_secret_key': 'mysecret', + 'memcache_secret_key': 'mysecret' } - auth = auth_token.AuthProtocol(FakeApp(), conf) - data = auth._protect_cache_value('mykey', 'This is a test!') + self.set_middleware(conf=conf) + data = self.middleware._protect_cache_value('mykey', + 'This is a test!') self.assertEqual(data, 'This is a test!') self.assertEqual( 'This is a test!', - auth._unprotect_cache_value('mykey', data)) + self.middleware._unprotect_cache_value('mykey', data)) def test_get_cache_key(self): conf = { - 'admin_token': 'admin_token1', 'auth_host': 'keystone.example.com', 'auth_port': 1234, + 'auth_admin_prefix': '/testadmin', 'memcache_servers': 'localhost:11211', - 'memcache_secret_key': 'mysecret', + 'memcache_secret_key': 'mysecret' } - auth = auth_token.AuthProtocol(FakeApp(), conf) + self.set_middleware(conf=conf) self.assertEqual( 'tokens/mytoken', - auth._get_cache_key('mytoken')) + self.middleware._get_cache_key('mytoken')) conf = { - 'admin_token': 'admin_token1', 'auth_host': 'keystone.example.com', 'auth_port': 1234, + 'auth_admin_prefix': '/testadmin', 'memcache_servers': 'localhost:11211', 'memcache_security_strategy': 'mac', - 'memcache_secret_key': 'mysecret', + 'memcache_secret_key': 'mysecret' } - auth = auth_token.AuthProtocol(FakeApp(), conf) + self.set_middleware(conf=conf) expected = 'tokens/' + memcache_crypt.hash_data('mytoken' + 'mysecret') - self.assertEqual(auth._get_cache_key('mytoken'), expected) + self.assertEqual(self.middleware._get_cache_key('mytoken'), expected) conf = { - 'admin_token': 'admin_token1', 'auth_host': 'keystone.example.com', 'auth_port': 1234, + 'auth_admin_prefix': '/testadmin', 'memcache_servers': 'localhost:11211', 'memcache_security_strategy': 'Encrypt', - 'memcache_secret_key': 'abc!', + 'memcache_secret_key': 'abc!' } - auth = auth_token.AuthProtocol(FakeApp(), conf) + self.set_middleware(conf=conf) expected = 'tokens/' + memcache_crypt.hash_data('mytoken' + 'abc!') - self.assertEqual(auth._get_cache_key('mytoken'), expected) + self.assertEqual(self.middleware._get_cache_key('mytoken'), expected) def test_assert_valid_memcache_protection_config(self): # test missing memcache_secret_key conf = { - 'admin_token': 'admin_token1', 'auth_host': 'keystone.example.com', 'auth_port': 1234, + 'auth_admin_prefix': '/testadmin', 'memcache_servers': 'localhost:11211', - 'memcache_security_strategy': 'Encrypt', + 'memcache_security_strategy': 'Encrypt' } - self.assertRaises(Exception, auth_token.AuthProtocol, - FakeApp(), conf) + self.assertRaises(Exception, self.set_middleware, conf) # test invalue memcache_security_strategy conf = { - 'admin_token': 'admin_token1', 'auth_host': 'keystone.example.com', 'auth_port': 1234, + 'auth_admin_prefix': '/testadmin', 'memcache_servers': 'localhost:11211', - 'memcache_security_strategy': 'whatever', + 'memcache_security_strategy': 'whatever' } - self.assertRaises(Exception, auth_token.AuthProtocol, - FakeApp(), conf) + self.assertRaises(Exception, self.set_middleware, conf) # test missing memcache_secret_key conf = { - 'admin_token': 'admin_token1', 'auth_host': 'keystone.example.com', 'auth_port': 1234, + 'auth_admin_prefix': '/testadmin', 'memcache_servers': 'localhost:11211', - 'memcache_security_strategy': 'mac', + 'memcache_security_strategy': 'mac' } - self.assertRaises(Exception, auth_token.AuthProtocol, - FakeApp(), conf) + self.assertRaises(Exception, self.set_middleware, conf) conf = { - 'admin_token': 'admin_token1', 'auth_host': 'keystone.example.com', 'auth_port': 1234, + 'auth_admin_prefix': '/testadmin', 'memcache_servers': 'localhost:11211', 'memcache_security_strategy': 'Encrypt', 'memcache_secret_key': '' } - self.assertRaises(Exception, auth_token.AuthProtocol, - FakeApp(), conf) + self.assertRaises(Exception, self.set_middleware, conf) conf = { - 'admin_token': 'admin_token1', 'auth_host': 'keystone.example.com', 'auth_port': 1234, + 'auth_admin_prefix': '/testadmin', 'memcache_servers': 'localhost:11211', 'memcache_security_strategy': 'mAc', 'memcache_secret_key': '' } - self.assertRaises(Exception, auth_token.AuthProtocol, - FakeApp(), conf) + self.assertRaises(Exception, self.set_middleware, conf) + + +class v2AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest): + """ v2 token specific tests. + + There are some differences between how the auth-token middleware handles + v2 and v3 tokens over and above the token formats, namely: + + - A v3 keystone server will auto scope a token to a user's default project + if no scope is specified. A v2 server assumes that the auth-token + middleware will do that. + - A v2 keystone server may issue a token without a catalog, even with a + tenant + + The tests below were originally part of the generic AuthTokenMiddlewareTest + class, but now, since they really are v2 specifc, they are included here. + + """ + def assert_unscoped_default_tenant_auto_scopes(self, token): + """Unscoped v2 requests with a default tenant should "auto-scope." + + The implied scope is the user's tenant ID. + + """ + req = webob.Request.blank('/') + req.headers['X-Auth-Token'] = token + body = self.middleware(req.environ, self.start_fake_response) + self.assertEqual(self.response_status, 200) + self.assertEqual(body, ['SUCCESS']) + self.assertTrue('keystone.token_info' in req.environ) + + def test_default_tenant_uuid_token(self): + self.assert_unscoped_default_tenant_auto_scopes(UUID_TOKEN_DEFAULT) + + def test_default_tenant_signed_token(self): + self.assert_unscoped_default_tenant_auto_scopes(SIGNED_TOKEN_SCOPED) + + def assert_unscoped_token_receives_401(self, token): + """Unscoped requests with no default tenant ID should be rejected.""" + req = webob.Request.blank('/') + req.headers['X-Auth-Token'] = token + self.middleware(req.environ, self.start_fake_response) + self.assertEqual(self.response_status, 401) + self.assertEqual(self.response_headers['WWW-Authenticate'], + "Keystone uri='https://keystone.example.com:1234'") + + def test_unscoped_uuid_token_receives_401(self): + self.assert_unscoped_token_receives_401(UUID_TOKEN_UNSCOPED) + + def test_unscoped_pki_token_receives_401(self): + self.assert_unscoped_token_receives_401(SIGNED_TOKEN_UNSCOPED) + + def test_request_prevent_service_catalog_injection(self): + req = webob.Request.blank('/') + req.headers['X-Service-Catalog'] = '[]' + req.headers['X-Auth-Token'] = UUID_TOKEN_NO_SERVICE_CATALOG + body = self.middleware(req.environ, self.start_fake_response) + self.assertEqual(self.response_status, 200) + self.assertFalse(req.headers.get('X-Service-Catalog')) + self.assertEqual(body, ['SUCCESS']) + + def test_valid_uuid_request_forced_to_2_0(self): + """ Test forcing auth_token to use lower api version. + + By installing the v3 http hander, auth_token will be get + a version list that looks like a v3 server - from which it + would normally chose v3.0 as the auth version. However, here + we specify v2.0 in the configuration - which should force + auth_token to use that version instead. + + """ + conf = { + 'auth_host': 'keystone.example.com', + 'auth_port': 1234, + 'auth_admin_prefix': '/testadmin', + 'signing_dir': CERTDIR, + 'auth_version': 'v2.0' + } + self.set_middleware(fake_http=v3FakeHTTPConnection, conf=conf) + # This tests will only work is auth_token has chosen to use the + # lower, v2, api version + req = webob.Request.blank('/') + req.headers['X-Auth-Token'] = UUID_TOKEN_DEFAULT + body = self.middleware(req.environ, self.start_fake_response) + self.assertEqual(self.response_status, 200) + self.assertEqual("/testadmin/v2.0/tokens/%s" % UUID_TOKEN_DEFAULT, + v3FakeHTTPConnection.last_requested_url) + + def test_invalid_auth_version_request(self): + conf = { + 'auth_host': 'keystone.example.com', + 'auth_port': 1234, + 'auth_admin_prefix': '/testadmin', + 'signing_dir': CERTDIR, + 'auth_version': 'v1.0' # v1.0 is no longer supported + } + self.assertRaises(Exception, self.set_middleware, conf) + + +class v3AuthTokenMiddlewareTest(AuthTokenMiddlewareTest): + """ Test auth_token middleware with v3 tokens. + + Re-execute the AuthTokenMiddlewareTest class tests, but with the + the auth_token middleware configured to expect v3 tokens back from + a keystone server. + + This is done by configuring the AuthTokenMiddlewareTest class via + its Setup(), passing in v3 style data that will then be used by + the tests themselves. This approach has been used to ensure we + really are running the same tests for both v2 and v3 tokens. + + There a few additional specific test for v3 only: + + - We allow an unscoped token to be validated (as unscoped), where + as for v2 tokens, the auth_token middleware is expected to try and + auto-scope it (and fail if there is no default tenant) + - Domain scoped tokens + + Since we don't specify an auth version for auth_token to use, by + definition we are thefore implicitely testing that it will use + the highest available auth version, i.e. v3.0 + + """ + def setUp(self): + token_dict = { + 'uuid_token_default': v3_UUID_TOKEN_DEFAULT, + 'uuid_token_unscoped': v3_UUID_TOKEN_UNSCOPED, + 'signed_token_scoped': SIGNED_v3_TOKEN_SCOPED, + 'revoked_token': REVOKED_v3_TOKEN, + 'revoked_token_hash': REVOKED_v3_TOKEN_HASH + } + super(v3AuthTokenMiddlewareTest, self).setUp( + fake_app=v3FakeApp, + fake_http=v3FakeHTTPConnection, + token_dict=token_dict, + fake_memcache=v3FakeMemcache, + fake_memcache_ring=v3FakeSwiftMemcacheRing) + + def assert_valid_last_url(self, token_id): + # Token ID is not part of the url in v3, so override + # this assert test in the base class + self.assertEqual('/testadmin/v3/auth/tokens', + v3FakeHTTPConnection.last_requested_url) + + def test_valid_unscoped_uuid_request(self): + # Remove items that won't be in an unscoped token + delta_expected_env = { + 'HTTP_X_PROJECT_ID': None, + 'HTTP_X_PROJECT_NAME': None, + 'HTTP_X_PROJECT_DOMAIN_ID': None, + 'HTTP_X_PROJECT_DOMAIN_NAME': None, + 'HTTP_X_TENANT_ID': None, + 'HTTP_X_TENANT_NAME': None, + 'HTTP_X_ROLES': '', + 'HTTP_X_TENANT': None, + 'HTTP_X_ROLE': '', + } + self.set_middleware(expected_env=delta_expected_env) + self.assert_valid_request_200(v3_UUID_TOKEN_UNSCOPED, + with_catalog=False) + self.assertEqual('/testadmin/v3/auth/tokens', + v3FakeHTTPConnection.last_requested_url) + + def test_domain_scoped_uuid_request(self): + # Modify items comapred to default token for a domain scope + delta_expected_env = { + 'HTTP_X_DOMAIN_ID': 'domain_id1', + 'HTTP_X_DOMAIN_NAME': 'domain_name1', + 'HTTP_X_PROJECT_ID': None, + 'HTTP_X_PROJECT_NAME': None, + 'HTTP_X_PROJECT_DOMAIN_ID': None, + 'HTTP_X_PROJECT_DOMAIN_NAME': None, + 'HTTP_X_TENANT_ID': None, + 'HTTP_X_TENANT_NAME': None, + 'HTTP_X_TENANT': None + } + self.set_middleware(expected_env=delta_expected_env) + self.assert_valid_request_200(v3_UUID_TOKEN_DOMAIN_SCOPED) + self.assertEqual('/testadmin/v3/auth/tokens', + v3FakeHTTPConnection.last_requested_url) class TokenEncodingTest(testtools.TestCase): |