diff --git a/build/cloudid/root/opt/yunion/share/saml/sp-metadata/aliyun_role.xml b/build/cloudid/root/opt/yunion/share/saml/sp-metadata/aliyun_role.xml
new file mode 100644
index 0000000000..bf18325c84
--- /dev/null
+++ b/build/cloudid/root/opt/yunion/share/saml/sp-metadata/aliyun_role.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+ MIIDXjCCAkagAwIBAgIEXHToLjANBgkqhkiG9w0BAQsFADBwMQswCQYDVQQGEwJDTjERMA8GA1UE
+ CBMISGFuZ3pob3UxKTAnBgNVBAsTIEFsaWJhYmEgQ2xvdWQgQ29tcHV0aW5nIENvLiBMdGQuMSMw
+ IQYDVQQDExp1cm46YWxpYmFiYTpjbG91ZGNvbXB1dGluZzAgFw0xOTAyMjYwNzE4MDZaGA8yMTE5
+ MDIwMjA3MTgwNlowcDELMAkGA1UEBhMCQ04xETAPBgNVBAgTCEhhbmd6aG91MSkwJwYDVQQLEyBB
+ bGliYWJhIENsb3VkIENvbXB1dGluZyBDby4gTHRkLjEjMCEGA1UEAxMadXJuOmFsaWJhYmE6Y2xv
+ dWRjb21wdXRpbmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDjD53ZENEHoYNXAOf
+ OIbVBJhj7SCWKmdjnbnxq8WAFWEeZtS6hZLPpWh1z7b0NjJkvCf5oFVqBJYbbW5kEKV+9CpV6VHZ
+ qOXmsIRlkvZB+Wnc3SduwiiiUR9JojSPxVQvSf4WLT+HDASlrBztuRV2vHj9utLbvy+6bgVBqF8g
+ emL9Pcif1robDH8HlqUcADXLAt18E4MbToldVoHjpFc6fAKUXujWH5feAL8g0CKlmf/JVlHLEtu4
+ vKPxBQ8sgkysk6EnrjXl6Q4a4t+vbPG5uczA1ouTkDupMCRlaWHIHaJL/AoDGabn8sVXdaVJUKC5
+ 54FNkRznBhRQll+Nuc2rAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAE8k4S4HvOglthJwF3aMQXGi
+ LKW6Becs9SljA0/5VtZQDrDf2By/1BIMvWfZ/dFnO+MylDLVdS6XWvWat/DW0fOGxU4s1WNfshX7
+ DJDGR2G1XgtGoDZEYIDahUp5katAPypCkY57fGZlI0d3nq46/2qT/Zpne+pFE3DI/x8klZMniniw
+ YjNXbG96y/M4DYi1J7RR8mLIfVvz5o1SMGT4Ta2p/USE2M9F6O7/zc2j62dQgXiYa9OONo31RiXR
+ TmvGEUNuQoBZhVrFIvOnNjIfFT7Xd3CUowwJKP1floserrx4B5jScRAi9yK3x2a2lhfc+PksXfTs
+ aqIr5TQL4OorLEE=
+
+
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+ urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+ urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
+ urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
+ urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName
+ urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos
+ urn:oasis:names:tc:SAML:2.0:nameid-format:entity
+
+
+ Alibaba Cloud Console Single Sign-On
+
+
+
+
+
+
+ Alibaba Cloud Computing Co. Ltd.
+ AlibabaCloud
+ https://www.aliyun.com
+
+
diff --git a/build/cloudid/root/opt/yunion/share/saml/sp-metadata/aws.xml b/build/cloudid/root/opt/yunion/share/saml/sp-metadata/aws.xml
new file mode 100644
index 0000000000..7e65b9debc
--- /dev/null
+++ b/build/cloudid/root/opt/yunion/share/saml/sp-metadata/aws.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+ MIIDbTCCAlWgAwIBAgIEFWIThjANBgkqhkiG9w0BAQsFADBnMR8wHQYDVQQDExZ1
+cm46YW1hem9uOndlYnNlcnZpY2VzMSIwIAYDVQQKExlBbWF6b24gV2ViIFNlcnZp
+Y2VzLCBJbmMuMRMwEQYDVQQIEwpXYXNoaW5ndG9uMQswCQYDVQQGEwJVUzAeFw0y
+MDA2MDIwMDAwMDBaFw0yMTA2MDIwMDAwMDBaMGcxHzAdBgNVBAMTFnVybjphbWF6
+b246d2Vic2VydmljZXMxIjAgBgNVBAoTGUFtYXpvbiBXZWIgU2VydmljZXMsIElu
+Yy4xEzARBgNVBAgTCldhc2hpbmd0b24xCzAJBgNVBAYTAlVTMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwKA4yDh4wAjlkhjJoXARGX9DQWsGTgp9GzoX
+NebntY+DjB8H6hIkYRR3++yEDY90qldbZzOaOKRm2jyL7i+MgNt+ktx6wYGD7HBg
+e8Z0Zh6eHRIwKbvu4/heMLdqqu+hlXkrC15T4N/LqiwT5JhPTrO4V1KG3ljR1ONG
+7aLblVH2aRENV+X9la/AVAPesUpCnoUcTkuCJrcwHrv6hqWmadW8pJy/ISCCM4Fe
+JuOAJD0whxg0qRFvE6xcdnKBTWjR03VHqJnyYQGVUKXOlgj8HiKO0JaJkoUAs5ox
+WCRBZwSQW+qbP1VKSUfFeBtTMx/TSg4nBRYHinego/e8O1mu6wIDAQABoyEwHzAd
+BgNVHQ4EFgQUz1SuekLVxYNvvLpUDRXc/gp2WdAwDQYJKoZIhvcNAQELBQADggEB
+AIkUzM9MXr11vp9gBOOXyqyj7sBgoUQHW3mEfKD3DkEOZWUPW5UI7NM9ZHvhdOwd
+C1noUK/QMjLkTHp0QB2bxCIpgThXSNFDYNRxf1/aUqTfxQ8a+i0q7l/utRiWyY3p
+kLOFx0n81a2bSNz+B0nPS338xNaBtuNjHedXjxN6BjUCUfFHFsQmg/y6ZbBRN/9P
++i2yy3CjLduh0yeD0Of3vAoIh19MuNgnIy1pMsK/H59E8bAaCFvND9fnsqAfH29W
+ppmt9c8QaiAya5R7akl/C0Rx8khQrba+wMcSultqLU+6YVIAAEKZ/S/ZfN74wn5X
+RXwSXp4MSmOKYqRWshvRegQ=
+
+
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+ urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+ urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
+ urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
+ urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName
+ urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos
+ urn:oasis:names:tc:SAML:2.0:nameid-format:entity
+
+
+ AWS Management Console Single Sign-On
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Amazon Web Services, Inc.
+ AWS
+ https://aws.amazon.com
+
+
+
diff --git a/build/cloudid/root/opt/yunion/share/saml/sp-metadata/aws_cn.xml b/build/cloudid/root/opt/yunion/share/saml/sp-metadata/aws_cn.xml
new file mode 100644
index 0000000000..057aa2a644
--- /dev/null
+++ b/build/cloudid/root/opt/yunion/share/saml/sp-metadata/aws_cn.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+ MIIDfzCCAmegAwIBAgIEY1tz5jANBgkqhkiG9w0BAQsFADBwMSgwJgYDVQQDEx91
+cm46YW1hem9uOndlYnNlcnZpY2VzOmNuLW5vcnRoMSIwIAYDVQQKExlBbWF6b24g
+V2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQIEwpXYXNoaW5ndG9uMQswCQYDVQQG
+EwJVUzAeFw0yMDA1MTMwMDAwMDBaFw0yMTA1MTMwMDAwMDBaMHAxKDAmBgNVBAMT
+H3VybjphbWF6b246d2Vic2VydmljZXM6Y24tbm9ydGgxIjAgBgNVBAoTGUFtYXpv
+biBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAgTCldhc2hpbmd0b24xCzAJBgNV
+BAYTAlVTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm2HLuzEnEGVT
+C5W215HWuwFLM54v3oHoNy52GGN79VLn7sxm2Z0K+AVijV/rxCbPaNxO6tm/MmN6
+XJkU8g8p7ghT6p+EN/61WG+KgoXIHcl/aZBbkoNTKKPXRvR9EEcyAPdLDBJVJHF5
+lgCkSMHBL3VcCcrpVxFCtUhW+vT3pie2KHLEVb5Ocpq9pO8dNvY2iM7TSIvRu0kL
+IZWUbRF0bP4cpDIVoMEsSJ3gFBCD3yf53UfU2dxjp2mnMjphPucHwml3AcsXN3XT
+Y5j2qksO5xXgFrCZz76l3P7emj5eHIvkxuieU/7u+cYu0HppP+4xszzqvUcoHFRA
+1fC/eFFRAwIDAQABoyEwHzAdBgNVHQ4EFgQUZkQfgy0NWq7kAO8mTQPeSDayJ3Ew
+DQYJKoZIhvcNAQELBQADggEBACmLLD2qBA+KCxv7CiCfznT8XWOLAVf9xP3YMk0N
+WCnugpd30d0YzxtI2PA29cNB2pyuGbaVg+zGqpsfAznaBHvnnEx1fJHNyfXxhAry
+0MdabmbvhMfJPUYRFx/7r6Km8d52e+nv28pP/WrbWs4I0Swm+Rwaj3t32EgCBxQS
+8WWEkG+L00slRgSPkfY8OgOM4QkBThHjGgw0TKFTEvQNVqXOJKQREq0MejyarUSC
+4am+OJeRcWKtPcRo1jUlGWG6eun0ybuw232ZEZHUv33B247aRMuvTgcRTz3s6tUE
+7RoeaMpl3/Y1MKtB2qURiu0Cib27Rl1mckhmfdA+awl3s4s=
+
+
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+ urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+ urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
+ urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
+ urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName
+ urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos
+ urn:oasis:names:tc:SAML:2.0:nameid-format:entity
+
+
+ AWS Management Console Single Sign-On
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Amazon Web Services, Inc.
+ AWS
+ https://aws.amazon.com
+
+
+
diff --git a/build/cloudid/root/opt/yunion/share/saml/sp-metadata/gcp.xml b/build/cloudid/root/opt/yunion/share/saml/sp-metadata/gcp.xml
new file mode 100644
index 0000000000..8f0b7fba49
--- /dev/null
+++ b/build/cloudid/root/opt/yunion/share/saml/sp-metadata/gcp.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+ urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+ urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
+ urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
+ urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName
+ urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos
+ urn:oasis:names:tc:SAML:2.0:nameid-format:entity
+
+
+
+
+
+
diff --git a/build/cloudid/root/opt/yunion/share/saml/sp-metadata/gcp_domain.xml b/build/cloudid/root/opt/yunion/share/saml/sp-metadata/gcp_domain.xml
new file mode 100644
index 0000000000..2c79e34b34
--- /dev/null
+++ b/build/cloudid/root/opt/yunion/share/saml/sp-metadata/gcp_domain.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+ urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+ urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
+ urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName
+ urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName
+ urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos
+ urn:oasis:names:tc:SAML:2.0:nameid-format:entity
+
+
+
+
+
+
diff --git a/build/cloudid/root/opt/yunion/share/saml/sp-metadata/huawei.xml b/build/cloudid/root/opt/yunion/share/saml/sp-metadata/huawei.xml
new file mode 100644
index 0000000000..1f32645524
--- /dev/null
+++ b/build/cloudid/root/opt/yunion/share/saml/sp-metadata/huawei.xml
@@ -0,0 +1,79 @@
+diBMwyuN633Q/kBf0M+SQZ4fNCI=k6+6QX8T5+S5wEIWlxwvd5a48xLWmp4Jd0bcs33vNaQVQ93YiztGcroUUqnLYK8uqAsmk2ZP4NtKFL/MyqoznrStglSi7uycwX6X3fswMwvkHJ9UVhH6Gp2Txl2JQ9/0vXy/tlXjQNFbfqzLhqxKC/K/PmKd684XkPYXlkiEJa6nGibu0gse9rvP2hE0G9bDMfdiyQnanfBvN+oNRte3xyI3DWh2P/jDTAPJMYzGM76JIneS8jLPa4gKDz5KcumG/8JV7GUWTDTZZ4ftujsjEuUFdPzKqwMYogMXOnTt8wbisSF8ZlLSMH/TIj6xAO9aSkEAo8HtlHJbekYlVA5Tz5IamLLzfaPpf7+NghLSuATIwY8/pvBZ8qhY8PVOjzRCeoEZJUrlFOcZ2CvO9zVKYdkEa1JC3mUW7CgeQc7G2/9niub7Vu00eyp9AAd9nkPfLoiWam8/yg2TBPRRJ9VKsv2UMFuITWrcFJayjezlTzY3dI3tI8lfoIMEVRaEP1v1D8XbxnxiaiKZXsGQHtpTSLc1ZL441jeZDLa661raUJDlGA6mBc1QukJklEocGg+Q+FeU33MJCaN/rIZCGvrjIZNng3yKFw1+R7/CqeJJlFWw0hPJQGiy6wfrDYSmhwuXJ2vQn3dTHjlT3kWsniiZtuOUi2TjHbq8gVHqlxmPxSs=MIIF6TCCA9GgAwIBAgIEPHSCijANBgkqhkiG9w0BAQsFADCBpDELMAkGA1UEBhMCQ04xEjAQBgNV
+BAgTCUd1YW5nRG9uZzERMA8GA1UEBxMIU2hlblpoZW4xJTAjBgNVBAoTHEh1YXdlaSBUZWNobm9s
+b2dpZXMgQ28uLCBMdGQxKDAmBgNVBAsTH1NlcnZpY2UgUHJvdmlkZXIgT3BlcmF0aW9uIERlcHQx
+HTAbBgNVBAMTFGF1dGguaHVhd2VpY2xvdWQuY29tMB4XDTE4MDYyMTEzMjUwMFoXDTI4MDYxODEz
+MjUwMFowgaQxCzAJBgNVBAYTAkNOMRIwEAYDVQQIEwlHdWFuZ0RvbmcxETAPBgNVBAcTCFNoZW5a
+aGVuMSUwIwYDVQQKExxIdWF3ZWkgVGVjaG5vbG9naWVzIENvLiwgTHRkMSgwJgYDVQQLEx9TZXJ2
+aWNlIFByb3ZpZGVyIE9wZXJhdGlvbiBEZXB0MR0wGwYDVQQDExRhdXRoLmh1YXdlaWNsb3VkLmNv
+bTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJisHObeLOPs2mJ4eJBcruMZl9FjvRQe
+DmofxgOcIcmybt6qlDqAv7275JMMQfQFcEoxH3GqcCTYqvSaSnHPaJB1xljPIKAWtd7p1ymevcSy
+F2HdCZ8gPJKg3Q6+ZjwTipKS/nZr7xmpUQ0WvwRkgZ7zfslAW+y5PCgkDrgmEjG92rLArj8iPhmu
+jajXaTPQKVHuZMxBzS735uo4yjVwYE3+0mE4HTZjK+6n6Ffu+JhLzhcKGulmrT/6qHisMbIXAZyE
+egBDavomb+5zu/CUQwii5IAPrRTwwegYpG4+uYJ2cHfrUdHqw9lSCSbQzu1yW1AS4zB16sjoHZdV
+rxYyktlswNmJ1/MyRH5bO90e2kvVwV4l34Hi5HEFvFFjL8TAsbN4mGvA9fgohXp30x97UdPVV8Ji
+NNAKZjdZSEdG9xqrTRfe4+LQg/hzLNSwsko3nDnH8qhCgtb8qIipQ3s7niCa53AQWYR82lEViols
+/dbWU9qYeldVvGNAgJSqHLB7qLwcQW78+2V1446KhQqzqPeLI4ANGaLFKw8fGzgh85RKOjrIetb4
+wAOZmhrrUJrRg47DYQQjNv3glDg53ijLPFunzRqUoqLphrZ1XpEA4y21OtTP6OMYAM0lSOj1gjvb
+ubTDo8XOQs5YGtTOyHn4CQ4GR8NNo7UrwVEmoZHA+AKbAgMBAAGjITAfMB0GA1UdDgQWBBTt05QW
+9dyXi7eMekKOH0bn4xKrkDANBgkqhkiG9w0BAQsFAAOCAgEACv9zgzUgxxQ8t9ldOXmirxzSOrHx
+MCL8SKsu+c+Y4hoHma5LFjylv6x76NWTAFSE6GgqfuNI/gPj/2AWqObAvsHd8lsPjJ96ZoSaTmS8
+NrtU6HuT1Lc+CmVfeGd3/G+KspQECjg2JeBrfyEw9B8KUAQV20DQukGfAHtKKQPOZmKm0Qm3ExWC
+eXz1TR2KP+Lrhny/yG43g4iVUKq65HFHs5cRzRk0iR0/NLpggl5+Op0rxMxBn+bCrnJBi0n9/PIM
+fWNhkEBl+B++EifPUQxQaOEsnxTgFo1O4ksK9hDFcLbr+1qCDgVIMkyC1xMBBikCgLvIdzy3SXBN
+ndIEUq+QgOORxLlq1WqrfLFO22TxZm8XaB5g36UMk5PGoVHGnjALReAHjC0C5sIiKMJSgQOPd71X
+mQSsw3G9NsMKf/H3xJXkq/b672ls/l1JBslm52DAk2k5UlLkf/1p4I7WHOfm5ZNpDjj1rTP6SiAc
+tWLtqXIU28fLa2sA+zHXA5acDGOm6eIrMme5HpsV/KoUOW1MXGugK59zofeueCFDGRfbyoS2lj0S
+W+CbJVa72CLf3xPh2nWH0cK9de+wyCx8uI0KGPyV4I9/XBLHhvkb3XPaUfnkzYkcrG/39cOaxuPF
+z+haXwI1lvI964zvmTgwdDjdf/0asA09S7EEK2KyzXUREM4=MIIF6TCCA9GgAwIBAgIEPHSCijANBgkqhkiG9w0BAQsFADCBpDELMAkGA1UEBhMCQ04xEjAQBgNV
+BAgTCUd1YW5nRG9uZzERMA8GA1UEBxMIU2hlblpoZW4xJTAjBgNVBAoTHEh1YXdlaSBUZWNobm9s
+b2dpZXMgQ28uLCBMdGQxKDAmBgNVBAsTH1NlcnZpY2UgUHJvdmlkZXIgT3BlcmF0aW9uIERlcHQx
+HTAbBgNVBAMTFGF1dGguaHVhd2VpY2xvdWQuY29tMB4XDTE4MDYyMTEzMjUwMFoXDTI4MDYxODEz
+MjUwMFowgaQxCzAJBgNVBAYTAkNOMRIwEAYDVQQIEwlHdWFuZ0RvbmcxETAPBgNVBAcTCFNoZW5a
+aGVuMSUwIwYDVQQKExxIdWF3ZWkgVGVjaG5vbG9naWVzIENvLiwgTHRkMSgwJgYDVQQLEx9TZXJ2
+aWNlIFByb3ZpZGVyIE9wZXJhdGlvbiBEZXB0MR0wGwYDVQQDExRhdXRoLmh1YXdlaWNsb3VkLmNv
+bTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJisHObeLOPs2mJ4eJBcruMZl9FjvRQe
+DmofxgOcIcmybt6qlDqAv7275JMMQfQFcEoxH3GqcCTYqvSaSnHPaJB1xljPIKAWtd7p1ymevcSy
+F2HdCZ8gPJKg3Q6+ZjwTipKS/nZr7xmpUQ0WvwRkgZ7zfslAW+y5PCgkDrgmEjG92rLArj8iPhmu
+jajXaTPQKVHuZMxBzS735uo4yjVwYE3+0mE4HTZjK+6n6Ffu+JhLzhcKGulmrT/6qHisMbIXAZyE
+egBDavomb+5zu/CUQwii5IAPrRTwwegYpG4+uYJ2cHfrUdHqw9lSCSbQzu1yW1AS4zB16sjoHZdV
+rxYyktlswNmJ1/MyRH5bO90e2kvVwV4l34Hi5HEFvFFjL8TAsbN4mGvA9fgohXp30x97UdPVV8Ji
+NNAKZjdZSEdG9xqrTRfe4+LQg/hzLNSwsko3nDnH8qhCgtb8qIipQ3s7niCa53AQWYR82lEViols
+/dbWU9qYeldVvGNAgJSqHLB7qLwcQW78+2V1446KhQqzqPeLI4ANGaLFKw8fGzgh85RKOjrIetb4
+wAOZmhrrUJrRg47DYQQjNv3glDg53ijLPFunzRqUoqLphrZ1XpEA4y21OtTP6OMYAM0lSOj1gjvb
+ubTDo8XOQs5YGtTOyHn4CQ4GR8NNo7UrwVEmoZHA+AKbAgMBAAGjITAfMB0GA1UdDgQWBBTt05QW
+9dyXi7eMekKOH0bn4xKrkDANBgkqhkiG9w0BAQsFAAOCAgEACv9zgzUgxxQ8t9ldOXmirxzSOrHx
+MCL8SKsu+c+Y4hoHma5LFjylv6x76NWTAFSE6GgqfuNI/gPj/2AWqObAvsHd8lsPjJ96ZoSaTmS8
+NrtU6HuT1Lc+CmVfeGd3/G+KspQECjg2JeBrfyEw9B8KUAQV20DQukGfAHtKKQPOZmKm0Qm3ExWC
+eXz1TR2KP+Lrhny/yG43g4iVUKq65HFHs5cRzRk0iR0/NLpggl5+Op0rxMxBn+bCrnJBi0n9/PIM
+fWNhkEBl+B++EifPUQxQaOEsnxTgFo1O4ksK9hDFcLbr+1qCDgVIMkyC1xMBBikCgLvIdzy3SXBN
+ndIEUq+QgOORxLlq1WqrfLFO22TxZm8XaB5g36UMk5PGoVHGnjALReAHjC0C5sIiKMJSgQOPd71X
+mQSsw3G9NsMKf/H3xJXkq/b672ls/l1JBslm52DAk2k5UlLkf/1p4I7WHOfm5ZNpDjj1rTP6SiAc
+tWLtqXIU28fLa2sA+zHXA5acDGOm6eIrMme5HpsV/KoUOW1MXGugK59zofeueCFDGRfbyoS2lj0S
+W+CbJVa72CLf3xPh2nWH0cK9de+wyCx8uI0KGPyV4I9/XBLHhvkb3XPaUfnkzYkcrG/39cOaxuPF
+z+haXwI1lvI964zvmTgwdDjdf/0asA09S7EEK2KyzXUREM4=MIIF6TCCA9GgAwIBAgIEPHSCijANBgkqhkiG9w0BAQsFADCBpDELMAkGA1UEBhMCQ04xEjAQBgNV
+BAgTCUd1YW5nRG9uZzERMA8GA1UEBxMIU2hlblpoZW4xJTAjBgNVBAoTHEh1YXdlaSBUZWNobm9s
+b2dpZXMgQ28uLCBMdGQxKDAmBgNVBAsTH1NlcnZpY2UgUHJvdmlkZXIgT3BlcmF0aW9uIERlcHQx
+HTAbBgNVBAMTFGF1dGguaHVhd2VpY2xvdWQuY29tMB4XDTE4MDYyMTEzMjUwMFoXDTI4MDYxODEz
+MjUwMFowgaQxCzAJBgNVBAYTAkNOMRIwEAYDVQQIEwlHdWFuZ0RvbmcxETAPBgNVBAcTCFNoZW5a
+aGVuMSUwIwYDVQQKExxIdWF3ZWkgVGVjaG5vbG9naWVzIENvLiwgTHRkMSgwJgYDVQQLEx9TZXJ2
+aWNlIFByb3ZpZGVyIE9wZXJhdGlvbiBEZXB0MR0wGwYDVQQDExRhdXRoLmh1YXdlaWNsb3VkLmNv
+bTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJisHObeLOPs2mJ4eJBcruMZl9FjvRQe
+DmofxgOcIcmybt6qlDqAv7275JMMQfQFcEoxH3GqcCTYqvSaSnHPaJB1xljPIKAWtd7p1ymevcSy
+F2HdCZ8gPJKg3Q6+ZjwTipKS/nZr7xmpUQ0WvwRkgZ7zfslAW+y5PCgkDrgmEjG92rLArj8iPhmu
+jajXaTPQKVHuZMxBzS735uo4yjVwYE3+0mE4HTZjK+6n6Ffu+JhLzhcKGulmrT/6qHisMbIXAZyE
+egBDavomb+5zu/CUQwii5IAPrRTwwegYpG4+uYJ2cHfrUdHqw9lSCSbQzu1yW1AS4zB16sjoHZdV
+rxYyktlswNmJ1/MyRH5bO90e2kvVwV4l34Hi5HEFvFFjL8TAsbN4mGvA9fgohXp30x97UdPVV8Ji
+NNAKZjdZSEdG9xqrTRfe4+LQg/hzLNSwsko3nDnH8qhCgtb8qIipQ3s7niCa53AQWYR82lEViols
+/dbWU9qYeldVvGNAgJSqHLB7qLwcQW78+2V1446KhQqzqPeLI4ANGaLFKw8fGzgh85RKOjrIetb4
+wAOZmhrrUJrRg47DYQQjNv3glDg53ijLPFunzRqUoqLphrZ1XpEA4y21OtTP6OMYAM0lSOj1gjvb
+ubTDo8XOQs5YGtTOyHn4CQ4GR8NNo7UrwVEmoZHA+AKbAgMBAAGjITAfMB0GA1UdDgQWBBTt05QW
+9dyXi7eMekKOH0bn4xKrkDANBgkqhkiG9w0BAQsFAAOCAgEACv9zgzUgxxQ8t9ldOXmirxzSOrHx
+MCL8SKsu+c+Y4hoHma5LFjylv6x76NWTAFSE6GgqfuNI/gPj/2AWqObAvsHd8lsPjJ96ZoSaTmS8
+NrtU6HuT1Lc+CmVfeGd3/G+KspQECjg2JeBrfyEw9B8KUAQV20DQukGfAHtKKQPOZmKm0Qm3ExWC
+eXz1TR2KP+Lrhny/yG43g4iVUKq65HFHs5cRzRk0iR0/NLpggl5+Op0rxMxBn+bCrnJBi0n9/PIM
+fWNhkEBl+B++EifPUQxQaOEsnxTgFo1O4ksK9hDFcLbr+1qCDgVIMkyC1xMBBikCgLvIdzy3SXBN
+ndIEUq+QgOORxLlq1WqrfLFO22TxZm8XaB5g36UMk5PGoVHGnjALReAHjC0C5sIiKMJSgQOPd71X
+mQSsw3G9NsMKf/H3xJXkq/b672ls/l1JBslm52DAk2k5UlLkf/1p4I7WHOfm5ZNpDjj1rTP6SiAc
+tWLtqXIU28fLa2sA+zHXA5acDGOm6eIrMme5HpsV/KoUOW1MXGugK59zofeueCFDGRfbyoS2lj0S
+W+CbJVa72CLf3xPh2nWH0cK9de+wyCx8uI0KGPyV4I9/XBLHhvkb3XPaUfnkzYkcrG/39cOaxuPF
+z+haXwI1lvI964zvmTgwdDjdf/0asA09S7EEK2KyzXUREM4=urn:oasis:names:tc:SAML:2.0:nameid-format:transient
\ No newline at end of file
diff --git a/build/cloudid/root/opt/yunion/share/saml/sp-metadata/qcloud.xml b/build/cloudid/root/opt/yunion/share/saml/sp-metadata/qcloud.xml
new file mode 100644
index 0000000000..07c0799c5e
--- /dev/null
+++ b/build/cloudid/root/opt/yunion/share/saml/sp-metadata/qcloud.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+ MIIEWzCCA0OgAwIBAgIJAP2Wa/iz6Zn+MA0GCSqGSIb3DQEBBQUAMIHDMQswCQYDVQQGEwJDTjESMBAGA1UECAwJZ3Vhbmdkb25nMREwDwYDVQQHDAhzaGVuemhlbjE2MDQGA1UECgwtVGVuY2VudCBUZWNobm9sb2d5IChTaGVuemhlbikgQ29tcGFueSBMaW1pdGVkMRYwFAYDVQQLDA1UZW5jZW50IENsb3VkMRowGAYDVQQDDBFjbG91ZC50ZW5jZW50LmNvbTEhMB8GCSqGSIb3DQEJARYScWNsb3VkQHRlbmNlbnQuY29tMB4XDTE4MDgxNTA2MjQzMVoXDTE5MDgxNTA2MjQzMVowgcMxCzAJBgNVBAYTAkNOMRIwEAYDVQQIDAlndWFuZ2RvbmcxETAPBgNVBAcMCHNoZW56aGVuMTYwNAYDVQQKDC1UZW5jZW50IFRlY2hub2xvZ3kgKFNoZW56aGVuKSBDb21wYW55IExpbWl0ZWQxFjAUBgNVBAsMDVRlbmNlbnQgQ2xvdWQxGjAYBgNVBAMMEWNsb3VkLnRlbmNlbnQuY29tMSEwHwYJKoZIhvcNAQkBFhJxY2xvdWRAdGVuY2VudC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7z2KMtR6GS4IzwfFYLSULCrKrr6iUVRlxUhn0y/CjpWXGg31nwvXYmJZaCwjUlorTd3pYDmStmd4xoL3xrGFnqX0xqKw7S+vbB9wo5tSHww15vkiYPanjsgeaiXihBBB6jvsV8+4iuVk8JA3x990/0wic/ZuKIhCI4mLCaQYLXbsYopUPQ5p+dwkCY9pYxP+zjOQathA0cjzKyzp9io2pcH2vtj7RzM+j4sLTsTC+OGfh7k9LQO5/fswQTQKr1B4tmtI8Cq7pUP0Fud1YLjMpod/NRVv1/xIF6cJFP8QARfmC45FPjHToORiXVUV8hii8l4EkXw2bAQO+wqodXJaZAgMBAAGjUDBOMB0GA1UdDgQWBBQ6e8nFUT7oKF+LbOYFH200WR5I0zAfBgNVHSMEGDAWgBQ6e8nFUT7oKF+LbOYFH200WR5I0zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBAIaZDjgCLkWq6fFpp/ViwdEBtLbKNNx2K28z91kTZNE+lepYagqWQIPpTGo69GmO9Qdf/mkspY3Y80Q3MpUO09V3gsiqXQBHD5ZfyRHwmqDjDudLA1SGzMjMT+48XuMq4fdonO1o5fyDh/My9w1LuRZzWw5ZE0JEpKgXq1ynQYz0uEip7KrUVUNoAS2n9jT8APfs16M4p5VRpIYlugnBwR9P2bZba+9fR/SPTa7FX4S8sU649wGPR+49uvic2kwc8FP6AXGurJZhd7mZ44j58q0M/LXm/6wiyUslZ/I0uGhxmRav0cTQz4T1dXKdh2pJZBwz8yMv2P6fuxcFlUeR6
+
+
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+ urn:oasis:names:tc:SAML:2.0:nameid-format:entity
+ urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+ urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
+ urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName
+
+
+
+
+
+
+
+
+ Tencent Cloud Services, Inc.
+ Tencent Cloud
+ https://cloud.tencent.com
+
+
diff --git a/build/docker/Dockerfile.cloudid b/build/docker/Dockerfile.cloudid
index 4e9a683e60..2ffcf573bf 100644
--- a/build/docker/Dockerfile.cloudid
+++ b/build/docker/Dockerfile.cloudid
@@ -1,3 +1,4 @@
FROM registry.cn-beijing.aliyuncs.com/yunionio/onecloud-base:v0.1
+COPY ./build/cloudid/root/opt/ /opt/
ADD ./_output/bin/cloudid /opt/yunion/bin/cloudid
diff --git a/cmd/aliyuncli/main.go b/cmd/aliyuncli/main.go
index 53344fe3ad..a26b4cabd1 100644
--- a/cmd/aliyuncli/main.go
+++ b/cmd/aliyuncli/main.go
@@ -28,9 +28,9 @@ import (
type BaseOptions struct {
Debug bool `help:"debug mode"`
Help bool `help:"Show help"`
- AccessKey string `help:"Access key" default:"$ALIYUN_ACCESS_KEY"`
- Secret string `help:"Secret" default:"$ALIYUN_SECRET"`
- RegionId string `help:"RegionId" default:"$ALIYUN_REGION"`
+ AccessKey string `help:"Access key" default:"$ALIYUN_ACCESS_KEY" metavar:"ALIYUN_ACCESS_KEY"`
+ Secret string `help:"Secret" default:"$ALIYUN_SECRET" metavar:"ALIYUN_SECRET"`
+ RegionId string `help:"RegionId" default:"$ALIYUN_REGION" metavar:"ALIYUN_REGION"`
SUBCOMMAND string `help:"aliyuncli subcommand" subcommand:"true"`
}
diff --git a/cmd/awscli/main.go b/cmd/awscli/main.go
index 1217f56dd8..e0f568b696 100644
--- a/cmd/awscli/main.go
+++ b/cmd/awscli/main.go
@@ -28,10 +28,10 @@ import (
type BaseOptions struct {
Help bool `help:"Show help"`
Debug bool `help:"debug mode"`
- AccessUrl string `help:"Access key" default:"$AWS_ACCESS_URL" choices:"ChinaCloud|InternationalCloud"`
- AccessKey string `help:"Access key" default:"$AWS_ACCESS_KEY"`
- Secret string `help:"Secret" default:"$AWS_SECRET"`
- RegionId string `help:"RegionId" default:"$AWS_REGION"`
+ AccessUrl string `help:"Access key" default:"$AWS_ACCESS_URL" choices:"ChinaCloud|InternationalCloud" metavar:"AWS_ACCESS_URL"`
+ AccessKey string `help:"Access key" default:"$AWS_ACCESS_KEY" metavar:"AWS_ACCESS_KEY"`
+ Secret string `help:"Secret" default:"$AWS_SECRET" metavar:"AWS_SECRET"`
+ RegionId string `help:"RegionId" default:"$AWS_REGION" metavar:"AWS_REGION"`
SUBCOMMAND string `help:"awscli subcommand" subcommand:"true"`
}
diff --git a/cmd/azurecli/main.go b/cmd/azurecli/main.go
index 4c9f4a3dad..3f338d3436 100644
--- a/cmd/azurecli/main.go
+++ b/cmd/azurecli/main.go
@@ -29,12 +29,12 @@ import (
type BaseOptions struct {
Help bool `help:"Show help"`
Debug bool `help:"debug mode"`
- DirectoryID string `help:"Azure account Directory ID/Tenant ID" default:"$AZURE_DIRECTORY_ID"`
- SubscriptionID string `help:"Azure account subscription ID" default:"$AZURE_SUBSCRIPTION_ID"`
- ApplicationID string `help:"Azure application ID" default:"$AZURE_APPLICATION_ID"`
- ApplicationKey string `help:"Azure application key" default:"$AZURE_APPLICATION_KEY"`
- RegionId string `help:"RegionId" default:"$AZURE_REGION_ID"`
- CloudEnv string `help:"Cloud Environment" default:"$AZURE_CLOUD_ENV" choices:"AzureGermanCloud|AzureChinaCloud|AzureUSGovernmentCloud|AzurePublicCloud"`
+ DirectoryID string `help:"Azure account Directory ID/Tenant ID" default:"$AZURE_DIRECTORY_ID" metavar:"AZURE_DIRECTORY_ID"`
+ SubscriptionID string `help:"Azure account subscription ID" default:"$AZURE_SUBSCRIPTION_ID" metavar:"AZURE_SUBSCRIPTION_ID"`
+ ApplicationID string `help:"Azure application ID" default:"$AZURE_APPLICATION_ID" metavar:"AZURE_APPLICATION_ID"`
+ ApplicationKey string `help:"Azure application key" default:"$AZURE_APPLICATION_KEY" metavar:"AZURE_APPLICATION_KEY"`
+ RegionId string `help:"RegionId" default:"$AZURE_REGION_ID" metavar:"AZURE_REGION_ID"`
+ CloudEnv string `help:"Cloud Environment" default:"$AZURE_CLOUD_ENV" choices:"AzureGermanCloud|AzureChinaCloud|AzureUSGovernmentCloud|AzurePublicCloud" metavar:"AZURE_CLOUD_ENV"`
SUBCOMMAND string `help:"azurecli subcommand" subcommand:"true"`
}
diff --git a/cmd/climc/shell/compute/cloudaccounts.go b/cmd/climc/shell/compute/cloudaccounts.go
index 4cb79e7fd3..56ecdb58d4 100644
--- a/cmd/climc/shell/compute/cloudaccounts.go
+++ b/cmd/climc/shell/compute/cloudaccounts.go
@@ -1168,4 +1168,12 @@ func init() {
return nil
})
+ R(&CloudaccountShowOptions{}, "cloud-account-saml", "Get saml info details of a cloud account", func(s *mcclient.ClientSession, args *CloudaccountShowOptions) error {
+ result, err := modules.Cloudaccounts.GetSpecific(s, args.ID, "saml", nil)
+ if err != nil {
+ return err
+ }
+ printObject(result)
+ return nil
+ })
}
diff --git a/cmd/esxicli/main.go b/cmd/esxicli/main.go
index 7114b230a5..1296370f8c 100644
--- a/cmd/esxicli/main.go
+++ b/cmd/esxicli/main.go
@@ -27,10 +27,10 @@ import (
type BaseOptions struct {
Help bool `help:"Show help"`
- Host string `help:"Host IP or NAME" default:"$VMWARE_HOST"`
- Port int `help:"Service port" default:"$VMWARE_PORT"`
- Account string `help:"VCenter or ESXi Account" default:"$VMWARE_ACCOUNT"`
- Password string `help:"Password" default:"$VMWARE_PASSWORD"`
+ Host string `help:"Host IP or NAME" default:"$VMWARE_HOST" metavar:"VMWARE_HOST"`
+ Port int `help:"Service port" default:"$VMWARE_PORT" metavar:"VMWARE_PORT"`
+ Account string `help:"VCenter or ESXi Account" default:"$VMWARE_ACCOUNT" metavar:"VMWARE_ACCOUNT"`
+ Password string `help:"Password" default:"$VMWARE_PASSWORD" metavar:"VMWARE_PASSWORD"`
SUBCOMMAND string `help:"aliyuncli subcommand" subcommand:"true"`
}
diff --git a/cmd/googlecli/main.go b/cmd/googlecli/main.go
index 6b8ab5965b..926e7da9ab 100644
--- a/cmd/googlecli/main.go
+++ b/cmd/googlecli/main.go
@@ -32,12 +32,12 @@ import (
type BaseOptions struct {
Debug bool `help:"debug mode"`
Help bool `help:"Show help"`
- AuthFile string `help:"google cloud auth json file path" default:"$GOOGLE_AUTH_FILE"`
- ClientEmail string `help:"Client email" default:"$GOOGLE_CLIENT_EMAIL"`
- ProjectID string `help:"Project ID" default:"$GOOGLE_PROJECT_ID"`
- PrivateKeyID string `help:"Private Key ID" default:"$GOOGLE_PRIVATE_KEY_ID"`
- PrivateKey string `help:"Private Key" default:"$GOOGLE_PRIVATE_KEY"`
- RegionID string `help:"RegionID" default:"$GOOGLE_REGION"`
+ AuthFile string `help:"google cloud auth json file path" default:"$GOOGLE_AUTH_FILE" metavar:"GOOGLE_AUTH_FILE"`
+ ClientEmail string `help:"Client email" default:"$GOOGLE_CLIENT_EMAIL" metavar:"GOOGLE_CLIENT_EMAIL"`
+ ProjectID string `help:"Project ID" default:"$GOOGLE_PROJECT_ID" metavar:"GOOGLE_PROJECT_ID"`
+ PrivateKeyID string `help:"Private Key ID" default:"$GOOGLE_PRIVATE_KEY_ID" metavar:"GOOGLE_PRIVATE_KEY_ID"`
+ PrivateKey string `help:"Private Key" default:"$GOOGLE_PRIVATE_KEY" metavar:"GOOGLE_PRIVATE_KEY"`
+ RegionID string `help:"RegionID" default:"$GOOGLE_REGION" metavar:"GOOGLE_REGION"`
SUBCOMMAND string `help:"googlecli subcommand" subcommand:"true"`
}
diff --git a/cmd/huaweicli/main.go b/cmd/huaweicli/main.go
index ba0778920f..a55826181c 100644
--- a/cmd/huaweicli/main.go
+++ b/cmd/huaweicli/main.go
@@ -28,11 +28,11 @@ import (
type BaseOptions struct {
Help bool `help:"Show help" default:"false"`
Debug bool `help:"Show debug" default:"false"`
- CloudEnv string `help:"Cloud environment" default:"$HUAWEI_CLOUD_ENV" choices:"ChinaCloud|InternationalCloud"`
- AccessKey string `help:"Access key" default:"$HUAWEI_ACCESS_KEY"`
- Secret string `help:"Secret" default:"$HUAWEI_SECRET"`
- RegionId string `help:"RegionId" default:"$HUAWEI_REGION"`
- ProjectId string `help:"RegionId" default:"$HUAWEI_PROJECT"`
+ CloudEnv string `help:"Cloud environment" default:"$HUAWEI_CLOUD_ENV" choices:"ChinaCloud|InternationalCloud" metavar:"HUAWEI_CLOUD_ENV"`
+ AccessKey string `help:"Access key" default:"$HUAWEI_ACCESS_KEY" metavar:"HUAWEI_ACCESS_KEY"`
+ Secret string `help:"Secret" default:"$HUAWEI_SECRET" metavar:"HUAWEI_SECRET"`
+ RegionId string `help:"RegionId" default:"$HUAWEI_REGION" metavar:"HUAWEI_REGION"`
+ ProjectId string `help:"RegionId" default:"$HUAWEI_PROJECT" metavar:"HUAWEI_PROJECT"`
SUBCOMMAND string `help:"huaweicli subcommand" subcommand:"true"`
}
diff --git a/cmd/openstackcli/main.go b/cmd/openstackcli/main.go
index 0b87dffc00..8560fd4bea 100644
--- a/cmd/openstackcli/main.go
+++ b/cmd/openstackcli/main.go
@@ -28,14 +28,14 @@ import (
type BaseOptions struct {
Debug bool `help:"debug mode"`
Help bool `help:"Show help"`
- AuthURL string `help:"Auth URL" default:"$OPENSTACK_AUTH_URL"`
- Username string `help:"Username" default:"$OPENSTACK_USERNAME"`
- Password string `help:"Password" default:"$OPENSTACK_PASSWORD"`
- Project string `help:"Project" default:"$OPENSTACK_PROJECT"`
- EndpointType string `help:"Project" default:"$OPENSTACK_ENDPOINT_TYPE|internal"`
- DomainName string `help:"Domain of user" default:"$OPENSTACK_DOMAIN_NAME|Default"`
- ProjectDomain string `help:"Domain of project" default:"$OPENSTACK_PROJECT_DOMAIN|Default"`
- RegionID string `help:"RegionId" default:"$OPENSTACK_REGION_ID"`
+ AuthURL string `help:"Auth URL" default:"$OPENSTACK_AUTH_URL" metavar:"OPENSTACK_AUTH_URL"`
+ Username string `help:"Username" default:"$OPENSTACK_USERNAME" metavar:"OPENSTACK_USERNAME"`
+ Password string `help:"Password" default:"$OPENSTACK_PASSWORD" metavar:"OPENSTACK_PASSWORD"`
+ Project string `help:"Project" default:"$OPENSTACK_PROJECT" metavar:"OPENSTACK_PROJECT"`
+ EndpointType string `help:"Project" default:"$OPENSTACK_ENDPOINT_TYPE|internal" metavar:"OPENSTACK_ENDPOINT_TYPE"`
+ DomainName string `help:"Domain of user" default:"$OPENSTACK_DOMAIN_NAME|Default" metavar:"OPENSTACK_DOMAIN_NAME"`
+ ProjectDomain string `help:"Domain of project" default:"$OPENSTACK_PROJECT_DOMAIN|Default" metavar:"OPENSTACK_PROJECT_DOMAIN"`
+ RegionID string `help:"RegionId" default:"$OPENSTACK_REGION_ID" metavar:"OPENSTACK_REGION_ID"`
SUBCOMMAND string `help:"openstackcli subcommand" subcommand:"true"`
}
diff --git a/cmd/qcloudcli/main.go b/cmd/qcloudcli/main.go
index f53db18a48..8de20de837 100644
--- a/cmd/qcloudcli/main.go
+++ b/cmd/qcloudcli/main.go
@@ -28,10 +28,10 @@ import (
type BaseOptions struct {
Debug bool `help:"debug mode"`
Help bool `help:"Show help"`
- AppID string `help:"AppID" default:"$QCLOUD_APPID"`
- SecretID string `help:"Secret" default:"$QCLOUD_SECRET_ID"`
- SecretKey string `help:"Access key" default:"$QCLOUD_SECRET_KEY"`
- RegionId string `help:"RegionId" default:"$QCLOUD_REGION"`
+ AppID string `help:"AppID" default:"$QCLOUD_APPID" metavar:"QCLOUD_APPID"`
+ SecretID string `help:"Secret" default:"$QCLOUD_SECRET_ID" metavar:"QCLOUD_SECRET_ID"`
+ SecretKey string `help:"Access key" default:"$QCLOUD_SECRET_KEY" metavar:"QCLOUD_SECRET_KEY"`
+ RegionId string `help:"RegionId" default:"$QCLOUD_REGION" metavar:"QCLOUD_REGION"`
SUBCOMMAND string `help:"azurecli subcommand" subcommand:"true"`
}
diff --git a/cmd/redfishcli/main.go b/cmd/redfishcli/main.go
index 15922a915b..4c685d3278 100644
--- a/cmd/redfishcli/main.go
+++ b/cmd/redfishcli/main.go
@@ -30,9 +30,9 @@ import (
type BaseOptions struct {
Debug bool `help:"debug mode"`
Help bool `help:"Show help"`
- Endpoint string `help:"Endpoint, usually https://" default:"$REDFISH_ENDPOINT"`
- Username string `help:"Username, usually root" default:"$REDFISH_USERNAME"`
- Password string `help:"Password" default:"$REDFISH_PASSWORD"`
+ Endpoint string `help:"Endpoint, usually https://" default:"$REDFISH_ENDPOINT" metavar:"REDFISH_ENDPOINT"`
+ Username string `help:"Username, usually root" default:"$REDFISH_USERNAME" metavar:"REDFISH_USERNAME"`
+ Password string `help:"Password" default:"$REDFISH_PASSWORD" metavar:"REDFISH_PASSWORD"`
SUBCOMMAND string `help:"s3cli subcommand" subcommand:"true"`
}
diff --git a/cmd/s3cli/main.go b/cmd/s3cli/main.go
index 2c6fee1903..5c99f9cc0b 100644
--- a/cmd/s3cli/main.go
+++ b/cmd/s3cli/main.go
@@ -32,10 +32,10 @@ import (
type BaseOptions struct {
Debug bool `help:"debug mode"`
Help bool `help:"Show help"`
- AccessUrl string `help:"Access url" default:"$S3_ACCESS_URL"`
- AccessKey string `help:"Access key" default:"$S3_ACCESS_KEY"`
- Secret string `help:"Secret" default:"$S3_SECRET"`
- Backend string `help:"Backend driver" default:"$S3_BACKEND"`
+ AccessUrl string `help:"Access url" default:"$S3_ACCESS_URL" metavar:"S3_ACCESS_URL"`
+ AccessKey string `help:"Access key" default:"$S3_ACCESS_KEY" metavar:"S3_ACCESS_KEY"`
+ Secret string `help:"Secret" default:"$S3_SECRET" metavar:"S3_SECRET"`
+ Backend string `help:"Backend driver" default:"$S3_BACKEND" metavar:"S3_BACKEND"`
SUBCOMMAND string `help:"s3cli subcommand" subcommand:"true"`
}
diff --git a/cmd/ucloudcli/main.go b/cmd/ucloudcli/main.go
index fd912e7e04..0b7d99cd67 100644
--- a/cmd/ucloudcli/main.go
+++ b/cmd/ucloudcli/main.go
@@ -29,10 +29,10 @@ type BaseOptions struct {
Help bool `help:"Show help" default:"false"`
Debug bool `help:"Show debug" default:"false"`
- AccessKey string `help:"Access key" default:"$UCLOUD_ACCESS_KEY"`
- Secret string `help:"Secret" default:"$UCLOUD_SECRET"`
- RegionId string `help:"RegionId" default:"$UCLOUD_REGION"`
- ProjectId string `help:"ProjectId" default:"$UCLOUD_PROJECT"`
+ AccessKey string `help:"Access key" default:"$UCLOUD_ACCESS_KEY" metavar:"UCLOUD_ACCESS_KEY"`
+ Secret string `help:"Secret" default:"$UCLOUD_SECRET" metavar:"UCLOUD_SECRET"`
+ RegionId string `help:"RegionId" default:"$UCLOUD_REGION" metavar:"UCLOUD_REGION"`
+ ProjectId string `help:"ProjectId" default:"$UCLOUD_PROJECT" metavar:"UCLOUD_PROJECT"`
SUBCOMMAND string `help:"ucloudcli subcommand" subcommand:"true"`
}
diff --git a/cmd/zstackcli/main.go b/cmd/zstackcli/main.go
index 4694b18b3a..00c8e96ba1 100644
--- a/cmd/zstackcli/main.go
+++ b/cmd/zstackcli/main.go
@@ -28,10 +28,10 @@ import (
type BaseOptions struct {
Debug bool `help:"debug mode"`
Help bool `help:"Show help"`
- AuthURL string `help:"Auth URL" default:"$ZSTACK_AUTH_URL"`
- Username string `help:"Username" default:"$ZSTACK_USERNAME"`
- Password string `help:"Password" default:"$ZSTACK_PASSWORD"`
- RegionID string `help:"RegionId" default:"$ZSTACK_REGION_ID"`
+ AuthURL string `help:"Auth URL" default:"$ZSTACK_AUTH_URL" metavar:"ZSTACK_AUTH_URL"`
+ Username string `help:"Username" default:"$ZSTACK_USERNAME" metavar:"ZSTACK_USERNAME"`
+ Password string `help:"Password" default:"$ZSTACK_PASSWORD" metavar:"ZSTACK_PASSWORD"`
+ RegionID string `help:"RegionId" default:"$ZSTACK_REGION_ID" metavar:"ZSTACK_REGION_ID"`
SUBCOMMAND string `help:"zstackcli subcommand" subcommand:"true"`
}
diff --git a/pkg/apigateway/app/app.go b/pkg/apigateway/app/app.go
index 6ae8d4700e..a3fc38ded3 100644
--- a/pkg/apigateway/app/app.go
+++ b/pkg/apigateway/app/app.go
@@ -16,6 +16,7 @@ package app
import (
"yunion.io/x/onecloud/pkg/apigateway/handler"
+ "yunion.io/x/onecloud/pkg/apis/cloudid"
"yunion.io/x/onecloud/pkg/appsrv"
)
@@ -29,6 +30,7 @@ type Application struct {
CSRFResourceHandler handler.IHandler
RPCHandler handler.IHandler
InfluxdbProxyHandler handler.IHandler
+ CloudIdSAMLHandler handler.IHandler
}
func NewApp(app *appsrv.Application) *Application {
@@ -66,11 +68,14 @@ func (app *Application) InitHandlers() *Application {
app.InfluxdbProxyHandler = handler.NewInfluxdbProxyHandler("/query")
+ app.CloudIdSAMLHandler = handler.NewProxyHandlerWithService(cloudid.SAML_IDP_PREFIX, cloudid.SERVICE_TYPE)
+
return app
}
func (app *Application) Bind() {
for _, h := range []handler.IHandler{
+ app.CloudIdSAMLHandler,
app.InfluxdbProxyHandler,
app.MiscHandler,
app.AuthHandler,
diff --git a/pkg/apigateway/constants/constants.go b/pkg/apigateway/constants/constants.go
index bc79f5833e..bbc87a3572 100644
--- a/pkg/apigateway/constants/constants.go
+++ b/pkg/apigateway/constants/constants.go
@@ -17,7 +17,6 @@ package constants
const (
AUTH_PREFIX = "Bearer "
AUTH_HEADER = "Authorization"
- AUTH_TOKEN = "AUTH_TOKEN"
YUNION_AUTH_COOKIE = "yunionauth"
REGION_COOKIE = "region"
)
diff --git a/pkg/apigateway/handler/auth.go b/pkg/apigateway/handler/auth.go
index 264d7cbb80..d23ed95421 100644
--- a/pkg/apigateway/handler/auth.go
+++ b/pkg/apigateway/handler/auth.go
@@ -31,7 +31,6 @@ import (
"yunion.io/x/onecloud/pkg/apigateway/options"
policytool "yunion.io/x/onecloud/pkg/apigateway/policy"
"yunion.io/x/onecloud/pkg/apis/compute"
- "yunion.io/x/onecloud/pkg/appctx"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/cloudcommon/policy"
"yunion.io/x/onecloud/pkg/httperrors"
@@ -46,11 +45,7 @@ import (
)
func AppContextToken(ctx context.Context) mcclient.TokenCredential {
- val := ctx.Value(appctx.AppContextKey(constants.AUTH_TOKEN))
- if val == nil {
- return nil
- }
- return val.(mcclient.TokenCredential)
+ return auth.FetchUserCredential(ctx, nil)
}
type AuthHandlers struct {
@@ -72,6 +67,7 @@ func (h *AuthHandlers) AddMethods() {
NewHP(h.getIdpSsoRedirectUri, "sso", "redirect", ""),
NewHP(h.listTotpRecoveryQuestions, "recovery"),
NewHP(h.handleSsoLogin, "ssologin"),
+ // oidc auth
NewHP(handleOIDCAuth, "oidc", "auth"),
NewHP(handleOIDCConfiguration, "oidc", ".well-known", "openid-configuration"),
NewHP(handleOIDCJWKeys, "oidc", "keys"),
@@ -95,6 +91,7 @@ func (h *AuthHandlers) AddMethods() {
NewHP(h.getResources, "scoped_resources"),
NewHP(fetchIdpBasicConfig, "idp", "", "info"),
NewHP(fetchIdpSAMLMetadata, "idp", "", "saml-metadata"),
+ // oidc
NewHP(handleOIDCUserInfo, "oidc", "user"),
)
h.AddByMethod(POST, FetchAuthToken,
diff --git a/pkg/apigateway/handler/middleware.go b/pkg/apigateway/handler/middleware.go
index a539ebd177..541c12ffc4 100644
--- a/pkg/apigateway/handler/middleware.go
+++ b/pkg/apigateway/handler/middleware.go
@@ -121,7 +121,7 @@ func fetchAndSetAuthContext(ctx context.Context, w http.ResponseWriter, r *http.
}
// no more send auth header, save auth info in cookie
// setAuthHeader(w, authHeader)
- ctx = context.WithValue(ctx, appctx.AppContextKey(constants.AUTH_TOKEN), token)
+ ctx = context.WithValue(ctx, appctx.APP_CONTEXT_KEY_AUTH_TOKEN, token)
return ctx, nil
}
diff --git a/pkg/apigateway/handler/proxy.go b/pkg/apigateway/handler/proxy.go
index 580d5c2e77..f0e2ed2876 100644
--- a/pkg/apigateway/handler/proxy.go
+++ b/pkg/apigateway/handler/proxy.go
@@ -19,21 +19,39 @@ import (
"net/http"
"net/url"
+ "yunion.io/x/onecloud/pkg/apis"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/proxy"
)
type InfluxdbProxyHandler struct {
- prefix string
+ prefix string
+ serviceName string
}
func NewInfluxdbProxyHandler(prefix string) *InfluxdbProxyHandler {
- return &InfluxdbProxyHandler{prefix: prefix}
+ return NewProxyHandlerWithService(prefix, apis.SERVICE_TYPE_INFLUXDB)
+}
+
+func NewProxyHandlerWithService(prefix string, serviceName string) *InfluxdbProxyHandler {
+ return &InfluxdbProxyHandler{
+ prefix: prefix,
+ serviceName: serviceName,
+ }
+}
+
+func requestManipulator(ctx context.Context, r *http.Request) (*http.Request, error) {
+ r.Header.Del("Cookie")
+ token := AppContextToken(ctx)
+ if token != nil {
+ r.Header.Set("X-Auth-Token", token.GetTokenString())
+ }
+ return r, nil
}
func (h *InfluxdbProxyHandler) Bind(app *appsrv.Application) {
- app.AddReverseProxyHandler(h.prefix, fetchReverseEndpoint("influxdb"))
+ app.AddReverseProxyHandler(h.prefix, fetchReverseEndpoint(h.serviceName), requestManipulator)
}
func getEndpointSchemeHost(endpoint string) (string, error) {
@@ -49,7 +67,7 @@ func getEndpointSchemeHost(endpoint string) (string, error) {
}
func fetchReverseEndpoint(serviceName string) *proxy.SEndpointFactory {
- f := func(ctx context.Context, w http.ResponseWriter, r *http.Request) (string, error) {
+ f := func(ctx context.Context, r *http.Request) (string, error) {
endpointType := "internalURL"
session := auth.GetAdminSession(ctx, FetchRegion(r), "")
ep, err := session.GetServiceURL(serviceName, endpointType)
diff --git a/pkg/apis/cloudid/cloudid.go b/pkg/apis/cloudid/cloudid.go
index 262b635bbf..3e9cfed33d 100644
--- a/pkg/apis/cloudid/cloudid.go
+++ b/pkg/apis/cloudid/cloudid.go
@@ -18,5 +18,6 @@ import "yunion.io/x/onecloud/pkg/apis"
const (
SERVICE_TYPE = apis.SERVICE_TYPE_CLOUDID
+ SAML_IDP_PREFIX = "/saml/idp"
SERVICE_VERSION = ""
)
diff --git a/pkg/apis/cloudid/doc.go b/pkg/apis/cloudid/doc.go
index 7d11f48f98..6c334b2c80 100644
--- a/pkg/apis/cloudid/doc.go
+++ b/pkg/apis/cloudid/doc.go
@@ -1 +1,15 @@
+// Copyright 2019 Yunion
+//
+// 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 cloudid // import "yunion.io/x/onecloud/pkg/apis/cloudid"
diff --git a/pkg/apis/compute/cloudaccount.go b/pkg/apis/compute/cloudaccount.go
index d546386bf0..a68250f470 100644
--- a/pkg/apis/compute/cloudaccount.go
+++ b/pkg/apis/compute/cloudaccount.go
@@ -399,3 +399,16 @@ type SubscriptonCreateInput struct {
type EnrollmentAccountQuery struct {
}
+
+type GetCloudaccountSamlOutput struct {
+ // cloudaccount SAML ServiceProvider entity ID
+ EntityId string `json:"entity_id"`
+ // redirect login URL for this cloudaccount
+ RedirectLoginUrl string `json:"redirect_login_url"`
+ // redirect logout URL for this cloudaccount
+ RedirectLogoutUrl string `json:"redirect_logout_url"`
+ // metadata URL for this cloudaccount
+ MetadataUrl string `json:"metadata_url"`
+ // initial SAML SSO login URL for this cloudaccount
+ InitLoginUrl string `json:"init_login_url"`
+}
diff --git a/pkg/apis/const.go b/pkg/apis/const.go
index 0de1d188c2..2a9b8da5a6 100644
--- a/pkg/apis/const.go
+++ b/pkg/apis/const.go
@@ -37,4 +37,5 @@ const (
SERVICE_TYPE_SERVICETREE = "servicetree"
SERVICE_TYPE_LOG = "log"
SERVICE_TYPE_REGION = "compute"
+ SERVICE_TYPE_INFLUXDB = "influxdb"
)
diff --git a/pkg/appctx/context.go b/pkg/appctx/context.go
index 3cfa1e10f9..34b4548a3c 100644
--- a/pkg/appctx/context.go
+++ b/pkg/appctx/context.go
@@ -41,6 +41,8 @@ const (
APP_CONTEXT_KEY_START_TIME = AppContextKey("starttime")
APP_CONTEXT_KEY_HOST_ID = AppContextKey("hostid")
+
+ APP_CONTEXT_KEY_AUTH_TOKEN = AppContextKey("X_AUTH_TOKEN")
)
func AppContextServiceName(ctx context.Context) string {
diff --git a/pkg/appsrv/appsrv.go b/pkg/appsrv/appsrv.go
index cfb77cbea2..91b93e7b83 100644
--- a/pkg/appsrv/appsrv.go
+++ b/pkg/appsrv/appsrv.go
@@ -143,8 +143,8 @@ func (app *Application) getRoot(method string) *RadixNode {
return v
}
-func (app *Application) AddReverseProxyHandler(prefix string, ef *proxy.SEndpointFactory) {
- handler := proxy.NewHTTPReverseProxy(ef).ServeHTTP
+func (app *Application) AddReverseProxyHandler(prefix string, ef *proxy.SEndpointFactory, m proxy.RequestManipulator) {
+ handler := proxy.NewHTTPReverseProxy(ef, m).ServeHTTP
for _, method := range []string{"GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"} {
app.AddHandler(method, prefix, handler)
}
diff --git a/pkg/appsrv/handlers.go b/pkg/appsrv/handlers.go
index 40a5592e0a..a87a0816f9 100644
--- a/pkg/appsrv/handlers.go
+++ b/pkg/appsrv/handlers.go
@@ -25,6 +25,8 @@ import (
type FilterHandler func(ctx context.Context, w http.ResponseWriter, r *http.Request)
+type TMiddleware func(handler FilterHandler) FilterHandler
+
func VersionHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, version.GetShortString())
}
diff --git a/pkg/cloudid/options/doc.go b/pkg/cloudid/options/doc.go
index 17ff666c96..66eb2a0f71 100644
--- a/pkg/cloudid/options/doc.go
+++ b/pkg/cloudid/options/doc.go
@@ -1 +1,15 @@
+// Copyright 2019 Yunion
+//
+// 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 options // import "yunion.io/x/onecloud/pkg/cloudid/options"
diff --git a/pkg/cloudid/options/options.go b/pkg/cloudid/options/options.go
index 780f9dfc1e..581c8169dd 100644
--- a/pkg/cloudid/options/options.go
+++ b/pkg/cloudid/options/options.go
@@ -25,6 +25,8 @@ type SCloudIdOptions struct {
CloudaccountSyncIntervalMinutes int `help:"frequency to sync region cloudaccount task" default:"3"`
SystemPoliciesSyncIntervalHours int `help:"frequency to sync region cloudaccount task" default:"24"`
CloudIdResourceSyncIntervalHours int `help:"frequency to sync region cloudpolicy task" default:"3"`
+
+ CloudSAMLMetadataPath string `help:"path to store SAML sp metadata file of cloud providers" default:"/opt/yunion/"`
}
var (
diff --git a/pkg/cloudid/policy/doc.go b/pkg/cloudid/policy/doc.go
index 9485bad10d..a0606a61c7 100644
--- a/pkg/cloudid/policy/doc.go
+++ b/pkg/cloudid/policy/doc.go
@@ -1 +1,15 @@
+// Copyright 2019 Yunion
+//
+// 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 policy // import "yunion.io/x/onecloud/pkg/cloudid/policy"
diff --git a/pkg/cloudid/saml/doc.go b/pkg/cloudid/saml/doc.go
new file mode 100644
index 0000000000..837002ab26
--- /dev/null
+++ b/pkg/cloudid/saml/doc.go
@@ -0,0 +1,15 @@
+// Copyright 2019 Yunion
+//
+// 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 saml // import "yunion.io/x/onecloud/pkg/cloudid/saml"
diff --git a/pkg/cloudid/saml/init.go b/pkg/cloudid/saml/init.go
new file mode 100644
index 0000000000..8e3cd8ddc7
--- /dev/null
+++ b/pkg/cloudid/saml/init.go
@@ -0,0 +1,82 @@
+// Copyright 2019 Yunion
+//
+// 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 saml
+
+import (
+ "os"
+
+ "yunion.io/x/pkg/errors"
+
+ "yunion.io/x/onecloud/pkg/appsrv"
+ "yunion.io/x/onecloud/pkg/cloudid/options"
+ "yunion.io/x/onecloud/pkg/httperrors"
+ "yunion.io/x/onecloud/pkg/util/samlutils"
+ "yunion.io/x/onecloud/pkg/util/samlutils/idp"
+ "yunion.io/x/onecloud/pkg/util/seclib2"
+)
+
+var (
+ saml *samlutils.SSAMLInstance
+ idpInstance *idp.SSAMLIdpInstance
+)
+
+func initSAMLInstance() error {
+ certfile := options.Options.SslCertfile
+ if len(options.Options.SslCaCerts) > 0 {
+ var err error
+ certfile, err = seclib2.MergeCaCertFiles(options.Options.SslCaCerts, options.Options.SslCertfile)
+ if err != nil {
+ return errors.Wrapf(httperrors.ErrInputParameter, "fail to merge ca+cert content: %s", err)
+ }
+ defer os.Remove(certfile)
+ }
+ if len(certfile) == 0 {
+ return errors.Wrap(httperrors.ErrInputParameter, "Missing ssl-certfile")
+ }
+ if len(options.Options.SslKeyfile) == 0 {
+ return errors.Wrap(httperrors.ErrInputParameter, "Missing ssl-keyfile")
+ }
+
+ var err error
+ saml, err = samlutils.NewSAMLInstance(options.Options.ApiServer, certfile, options.Options.SslKeyfile)
+ if err != nil {
+ return errors.Wrap(err, "samlutils.NewSAMLInstance")
+ }
+
+ return nil
+}
+
+func SAMLInstance() *samlutils.SSAMLInstance {
+ if saml.GetEntityId() != options.Options.ApiServer {
+ saml.SetEntityId(options.Options.ApiServer)
+ }
+ return saml
+}
+
+func IsSAMLEnabled() bool {
+ return saml != nil
+}
+
+func InitSAML(app *appsrv.Application, prefix string) error {
+ err := initSAMLInstance()
+ if err != nil {
+ return errors.Wrap(err, "initSAMLInstance")
+ }
+ err = initSAMLIdp(app, prefix)
+ if err != nil {
+ return errors.Wrap(err, "initSAMLIdp")
+ }
+ return nil
+}
diff --git a/pkg/cloudid/saml/initidp.go b/pkg/cloudid/saml/initidp.go
new file mode 100644
index 0000000000..f226da30ec
--- /dev/null
+++ b/pkg/cloudid/saml/initidp.go
@@ -0,0 +1,113 @@
+// Copyright 2019 Yunion
+//
+// 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 saml
+
+import (
+ "context"
+ "fmt"
+ "io/ioutil"
+ "path"
+
+ "yunion.io/x/log"
+ "yunion.io/x/pkg/errors"
+
+ "yunion.io/x/onecloud/pkg/appsrv"
+ "yunion.io/x/onecloud/pkg/cloudid/options"
+ "yunion.io/x/onecloud/pkg/cloudid/saml/providers"
+ "yunion.io/x/onecloud/pkg/httperrors"
+ "yunion.io/x/onecloud/pkg/mcclient/auth"
+ "yunion.io/x/onecloud/pkg/util/httputils"
+ "yunion.io/x/onecloud/pkg/util/samlutils"
+ "yunion.io/x/onecloud/pkg/util/samlutils/idp"
+)
+
+func initSAMLIdp(app *appsrv.Application, prefix string) error {
+ spFunc := func(ctx context.Context, idpId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
+ token := auth.FetchUserCredential(ctx, nil)
+ log.Debugf("Recive SP initiated Login: %s", sp.GetEntityId())
+ data := samlutils.SSAMLSpInitiatedLoginData{}
+ driver := providers.FindDriver(sp.GetEntityId())
+ if driver == nil {
+ return data, errors.Wrapf(httperrors.ErrResourceNotFound, "entityID %s not found", sp.GetEntityId())
+ }
+ data, err := driver.GetSpInitiatedLoginData(idpId, token.GetUserId(), sp)
+ if err != nil {
+ return data, errors.Wrap(err, "driver.GetSpInitiatedLoginData")
+ }
+ return data, nil
+ /*
+ switch sp.GetEntityId() {
+ case SAML_ENTITY_ID_HUAWEI_CLOUD: // 华为云 SSO
+
+ case SAML_ENTITY_ID_TENCENT_CLOUD: // 腾讯云 role SSO
+
+ }
+ return data
+ */
+ }
+
+ idpFunc := func(ctx context.Context, sp *idp.SSAMLServiceProvider, idpId string) (samlutils.SSAMLIdpInitiatedLoginData, error) {
+ token := auth.FetchUserCredential(ctx, nil)
+ log.Debugf("Recive IDP initiated Login: %s", sp.GetEntityId())
+ data := samlutils.SSAMLIdpInitiatedLoginData{}
+ driver := providers.FindDriver(sp.GetEntityId())
+ if driver == nil {
+ return data, errors.Wrapf(httperrors.ErrResourceNotFound, "entityID %s not found", sp.GetEntityId())
+ }
+ data, err := driver.GetIdpInitiatedLoginData(idpId, token.GetUserId(), sp)
+ if err != nil {
+ return data, errors.Wrap(err, "driver.GetIdpInitiatedLoginData")
+ }
+ return data, nil
+ }
+
+ logoutFunc := func(ctx context.Context, idpId string) string {
+ return fmt.Sprintf(``, options.Options.ApiServer)
+ }
+
+ idpInst := idp.NewIdpInstance(saml, spFunc, idpFunc, logoutFunc)
+ for entityId, drvFactory := range providers.AllDrivers() {
+ filePath := path.Join(options.Options.CloudSAMLMetadataPath, drvFactory.GetMetadataFilename())
+ metaBytes, err := ioutil.ReadFile(filePath)
+ if err != nil || len(metaBytes) == 0 {
+ metaUrl := drvFactory.GetMetadataUrl()
+ if len(metaUrl) > 0 {
+ log.Debugf("[%s] metadata file load failed, try download from %s", entityId, metaUrl)
+ httpcli := httputils.GetDefaultClient()
+ resp, err := httpcli.Get(metaUrl)
+ if err != nil {
+ return errors.Wrapf(err, "http get %s fail", metaUrl)
+ }
+ metaBytes, err = ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return errors.Wrapf(err, "read body %s fail", metaUrl)
+ }
+ } else {
+ return errors.Wrapf(err, "read file %s fail", filePath)
+ }
+ }
+ err = idpInst.AddSPMetadata(metaBytes)
+ if err != nil {
+ return errors.Wrapf(err, "AddSPMetadata %s", metaBytes)
+ }
+ }
+
+ idpInst.AddHandlers(app, prefix, auth.Authenticate)
+ idpInst.SetHtmlTemplate(`正在跳转到云控制台,请等待。。。
$FORM$`)
+
+ idpInstance = idpInst
+
+ return nil
+}
diff --git a/pkg/cloudid/saml/load.go b/pkg/cloudid/saml/load.go
new file mode 100644
index 0000000000..02e9b5f4ac
--- /dev/null
+++ b/pkg/cloudid/saml/load.go
@@ -0,0 +1,24 @@
+// Copyright 2019 Yunion
+//
+// 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 saml
+
+import (
+ _ "yunion.io/x/onecloud/pkg/cloudid/saml/providers/aliyun"
+ _ "yunion.io/x/onecloud/pkg/cloudid/saml/providers/aws"
+ _ "yunion.io/x/onecloud/pkg/cloudid/saml/providers/awscn"
+ _ "yunion.io/x/onecloud/pkg/cloudid/saml/providers/google"
+ _ "yunion.io/x/onecloud/pkg/cloudid/saml/providers/huawei"
+ _ "yunion.io/x/onecloud/pkg/cloudid/saml/providers/qcloud"
+)
diff --git a/pkg/cloudid/saml/providers/aliyun/doc.go b/pkg/cloudid/saml/providers/aliyun/doc.go
new file mode 100644
index 0000000000..5e38945935
--- /dev/null
+++ b/pkg/cloudid/saml/providers/aliyun/doc.go
@@ -0,0 +1,15 @@
+// Copyright 2019 Yunion
+//
+// 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 aliyun // import "yunion.io/x/onecloud/pkg/cloudid/saml/providers/aliyun"
diff --git a/pkg/cloudid/saml/providers/aliyun/driver.go b/pkg/cloudid/saml/providers/aliyun/driver.go
new file mode 100644
index 0000000000..c17cad0020
--- /dev/null
+++ b/pkg/cloudid/saml/providers/aliyun/driver.go
@@ -0,0 +1,47 @@
+// Copyright 2019 Yunion
+//
+// 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 aliyun
+
+import (
+ "yunion.io/x/pkg/errors"
+
+ "yunion.io/x/onecloud/pkg/util/samlutils"
+ "yunion.io/x/onecloud/pkg/util/samlutils/idp"
+)
+
+func (d *SAliyunSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
+ // TODO
+ data := samlutils.SSAMLIdpInitiatedLoginData{}
+ data.NameId = "ecsossreadonly"
+ data.NameIdFormat = samlutils.NAME_ID_FORMAT_PERSISTENT
+ data.AudienceRestriction = sp.GetEntityId()
+ for k, v := range map[string]string{
+ "https://www.aliyun.com/SAML-Role/Attributes/Role": "acs:ram::1123247935774897:role/administrator,acs:ram::1123247935774897:saml-provider/saml.yunion.io",
+ "https://www.aliyun.com/SAML-Role/Attributes/RoleSessionName": "ecsossreadonly",
+ "https://www.aliyun.com/SAML-Role/Attributes/SessionDuration": "1800",
+ } {
+ data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{
+ Name: k,
+ Values: []string{v},
+ })
+ }
+ data.RelayState = "https://homenew.console.aliyun.com/"
+ return data, nil
+}
+
+func (d *SAliyunSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
+ // not supported
+ return samlutils.SSAMLSpInitiatedLoginData{}, errors.ErrNotSupported
+}
diff --git a/pkg/cloudid/saml/providers/aliyun/factory.go b/pkg/cloudid/saml/providers/aliyun/factory.go
new file mode 100644
index 0000000000..bf5f194459
--- /dev/null
+++ b/pkg/cloudid/saml/providers/aliyun/factory.go
@@ -0,0 +1,38 @@
+// Copyright 2019 Yunion
+//
+// 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 aliyun
+
+import (
+ "yunion.io/x/onecloud/pkg/cloudid/saml/providers"
+ "yunion.io/x/onecloud/pkg/cloudprovider"
+)
+
+type SAliyunSAMLDriver struct{}
+
+func (d *SAliyunSAMLDriver) GetEntityID() string {
+ return cloudprovider.SAML_ENTITY_ID_ALIYUN_ROLE
+}
+
+func (d *SAliyunSAMLDriver) GetMetadataFilename() string {
+ return "aliyun_role.xml"
+}
+
+func (d *SAliyunSAMLDriver) GetMetadataUrl() string {
+ return "https://signin.aliyun.com/saml-role/sp-metadata.xml"
+}
+
+func init() {
+ providers.Register(&SAliyunSAMLDriver{})
+}
diff --git a/pkg/cloudid/saml/providers/aws/doc.go b/pkg/cloudid/saml/providers/aws/doc.go
new file mode 100644
index 0000000000..7b67ba4a19
--- /dev/null
+++ b/pkg/cloudid/saml/providers/aws/doc.go
@@ -0,0 +1,15 @@
+// Copyright 2019 Yunion
+//
+// 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 aws // import "yunion.io/x/onecloud/pkg/cloudid/saml/providers/aws"
diff --git a/pkg/cloudid/saml/providers/aws/driver.go b/pkg/cloudid/saml/providers/aws/driver.go
new file mode 100644
index 0000000000..bfae216057
--- /dev/null
+++ b/pkg/cloudid/saml/providers/aws/driver.go
@@ -0,0 +1,66 @@
+// Copyright 2019 Yunion
+//
+// 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 aws
+
+import (
+ "yunion.io/x/pkg/errors"
+
+ "yunion.io/x/onecloud/pkg/util/samlutils"
+ "yunion.io/x/onecloud/pkg/util/samlutils/idp"
+)
+
+func (d *SAWSSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
+ // TODO
+ data := samlutils.SSAMLIdpInitiatedLoginData{}
+
+ data.NameId = "ec2s3readonly"
+ data.NameIdFormat = samlutils.NAME_ID_FORMAT_PERSISTENT
+ data.AudienceRestriction = "https://signin.aws.amazon.com/saml"
+ for _, v := range []struct {
+ name string
+ friendlyName string
+ value string
+ }{
+ {
+ name: "https://aws.amazon.com/SAML/Attributes/Role",
+ friendlyName: "RoleEntitlement",
+ value: "arn:aws:iam::285906155448:role/ec2s3readonly,arn:aws:iam::285906155448:saml-provider/saml.yunion.cn",
+ },
+ {
+ name: "https://aws.amazon.com/SAML/Attributes/RoleSessionName",
+ friendlyName: "RoleSessionName",
+ value: "ec2s3readonly",
+ },
+ {
+ name: "urn:oid:1.3.6.1.4.1.5923.1.1.1.3",
+ friendlyName: "eduPersonOrgDN",
+ value: "ec2s3readonly",
+ },
+ } {
+ data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{
+ Name: v.name,
+ FriendlyName: v.friendlyName,
+ Values: []string{v.value},
+ })
+ }
+ data.RelayState = "https://console.aws.amazon.com/"
+
+ return data, nil
+}
+
+func (d *SAWSSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
+ // not supported
+ return samlutils.SSAMLSpInitiatedLoginData{}, errors.ErrNotSupported
+}
diff --git a/pkg/cloudid/saml/providers/aws/factory.go b/pkg/cloudid/saml/providers/aws/factory.go
new file mode 100644
index 0000000000..1d1818054c
--- /dev/null
+++ b/pkg/cloudid/saml/providers/aws/factory.go
@@ -0,0 +1,38 @@
+// Copyright 2019 Yunion
+//
+// 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 aws
+
+import (
+ "yunion.io/x/onecloud/pkg/cloudid/saml/providers"
+ "yunion.io/x/onecloud/pkg/cloudprovider"
+)
+
+type SAWSSAMLDriver struct{}
+
+func (d *SAWSSAMLDriver) GetEntityID() string {
+ return cloudprovider.SAML_ENTITY_ID_AWS
+}
+
+func (d *SAWSSAMLDriver) GetMetadataFilename() string {
+ return "aws.xml"
+}
+
+func (d *SAWSSAMLDriver) GetMetadataUrl() string {
+ return "https://signin.aws.amazon.com/static/saml-metadata.xml"
+}
+
+func init() {
+ providers.Register(&SAWSSAMLDriver{})
+}
diff --git a/pkg/cloudid/saml/providers/awscn/doc.go b/pkg/cloudid/saml/providers/awscn/doc.go
new file mode 100644
index 0000000000..652b0b4631
--- /dev/null
+++ b/pkg/cloudid/saml/providers/awscn/doc.go
@@ -0,0 +1,15 @@
+// Copyright 2019 Yunion
+//
+// 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 awscn // import "yunion.io/x/onecloud/pkg/cloudid/saml/providers/awscn"
diff --git a/pkg/cloudid/saml/providers/awscn/driver.go b/pkg/cloudid/saml/providers/awscn/driver.go
new file mode 100644
index 0000000000..8e3459b677
--- /dev/null
+++ b/pkg/cloudid/saml/providers/awscn/driver.go
@@ -0,0 +1,66 @@
+// Copyright 2019 Yunion
+//
+// 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 awscn
+
+import (
+ "yunion.io/x/pkg/errors"
+
+ "yunion.io/x/onecloud/pkg/util/samlutils"
+ "yunion.io/x/onecloud/pkg/util/samlutils/idp"
+)
+
+func (d *SAWSCNSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
+ // TODO
+ data := samlutils.SSAMLIdpInitiatedLoginData{}
+
+ data.NameId = "ec2s3readonly"
+ data.NameIdFormat = samlutils.NAME_ID_FORMAT_PERSISTENT
+ data.AudienceRestriction = "https://signin.amazonaws.cn/saml"
+ for _, v := range []struct {
+ name string
+ friendlyName string
+ value string
+ }{
+ {
+ name: "https://aws.amazon.com/SAML/Attributes/Role",
+ friendlyName: "RoleEntitlement",
+ value: "arn:aws-cn:iam::248697896586:role/ec2s3readonly,arn:aws-cn:iam::248697896586:saml-provider/saml.yunion.io",
+ },
+ {
+ name: "https://aws.amazon.com/SAML/Attributes/RoleSessionName",
+ friendlyName: "RoleSessionName",
+ value: "ec2s3readonly",
+ },
+ {
+ name: "urn:oid:1.3.6.1.4.1.5923.1.1.1.3",
+ friendlyName: "eduPersonOrgDN",
+ value: "ec2s3readonly",
+ },
+ } {
+ data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{
+ Name: v.name,
+ FriendlyName: v.friendlyName,
+ Values: []string{v.value},
+ })
+ }
+ data.RelayState = "https://console.amazonaws.cn/"
+
+ return data, nil
+}
+
+func (d *SAWSCNSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
+ // not supported
+ return samlutils.SSAMLSpInitiatedLoginData{}, errors.ErrNotSupported
+}
diff --git a/pkg/cloudid/saml/providers/awscn/factory.go b/pkg/cloudid/saml/providers/awscn/factory.go
new file mode 100644
index 0000000000..473fb57a67
--- /dev/null
+++ b/pkg/cloudid/saml/providers/awscn/factory.go
@@ -0,0 +1,38 @@
+// Copyright 2019 Yunion
+//
+// 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 awscn
+
+import (
+ "yunion.io/x/onecloud/pkg/cloudid/saml/providers"
+ "yunion.io/x/onecloud/pkg/cloudprovider"
+)
+
+type SAWSCNSAMLDriver struct{}
+
+func (d *SAWSCNSAMLDriver) GetEntityID() string {
+ return cloudprovider.SAML_ENTITY_ID_AWS_CN
+}
+
+func (d *SAWSCNSAMLDriver) GetMetadataFilename() string {
+ return "aws_cn.xml"
+}
+
+func (d *SAWSCNSAMLDriver) GetMetadataUrl() string {
+ return "https://signin.amazonaws.cn/static/saml-metadata.xml"
+}
+
+func init() {
+ providers.Register(&SAWSCNSAMLDriver{})
+}
diff --git a/pkg/cloudid/saml/providers/doc.go b/pkg/cloudid/saml/providers/doc.go
new file mode 100644
index 0000000000..e1debc3e6b
--- /dev/null
+++ b/pkg/cloudid/saml/providers/doc.go
@@ -0,0 +1,15 @@
+// Copyright 2019 Yunion
+//
+// 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 providers // import "yunion.io/x/onecloud/pkg/cloudid/saml/providers"
diff --git a/pkg/cloudid/saml/providers/google/doc.go b/pkg/cloudid/saml/providers/google/doc.go
new file mode 100644
index 0000000000..c50fd369be
--- /dev/null
+++ b/pkg/cloudid/saml/providers/google/doc.go
@@ -0,0 +1,15 @@
+// Copyright 2019 Yunion
+//
+// 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 google // import "yunion.io/x/onecloud/pkg/cloudid/saml/providers/google"
diff --git a/pkg/cloudid/saml/providers/google/driver.go b/pkg/cloudid/saml/providers/google/driver.go
new file mode 100644
index 0000000000..5778b5a7a0
--- /dev/null
+++ b/pkg/cloudid/saml/providers/google/driver.go
@@ -0,0 +1,48 @@
+// Copyright 2019 Yunion
+//
+// 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 google
+
+import (
+ "yunion.io/x/onecloud/pkg/httperrors"
+ "yunion.io/x/onecloud/pkg/util/samlutils"
+ "yunion.io/x/onecloud/pkg/util/samlutils/idp"
+)
+
+func (d *SGoogleSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
+ // not supported
+ data := samlutils.SSAMLIdpInitiatedLoginData{}
+
+ return data, httperrors.ErrNotSupported
+}
+
+func (d *SGoogleSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
+ // TODO
+ data := samlutils.SSAMLSpInitiatedLoginData{}
+
+ data.NameId = "qiujian@yunion-hk.com"
+ data.NameIdFormat = samlutils.NAME_ID_FORMAT_EMAIL
+ data.AudienceRestriction = sp.GetEntityId()
+ for k, v := range map[string]string{
+ "user.email": "qiujian@yunion-hk.com",
+ } {
+ data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{
+ Name: k, FriendlyName: k,
+ NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
+ Values: []string{v},
+ })
+ }
+
+ return data, nil
+}
diff --git a/pkg/cloudid/saml/providers/google/factory.go b/pkg/cloudid/saml/providers/google/factory.go
new file mode 100644
index 0000000000..a058417a4a
--- /dev/null
+++ b/pkg/cloudid/saml/providers/google/factory.go
@@ -0,0 +1,38 @@
+// Copyright 2019 Yunion
+//
+// 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 google
+
+import (
+ "yunion.io/x/onecloud/pkg/cloudid/saml/providers"
+ "yunion.io/x/onecloud/pkg/cloudprovider"
+)
+
+type SGoogleSAMLDriver struct{}
+
+func (d *SGoogleSAMLDriver) GetEntityID() string {
+ return cloudprovider.SAML_ENTITY_ID_GOOGLE
+}
+
+func (d *SGoogleSAMLDriver) GetMetadataFilename() string {
+ return "gcp.xml"
+}
+
+func (d *SGoogleSAMLDriver) GetMetadataUrl() string {
+ return ""
+}
+
+func init() {
+ providers.Register(&SGoogleSAMLDriver{})
+}
diff --git a/pkg/cloudid/saml/providers/huawei/doc.go b/pkg/cloudid/saml/providers/huawei/doc.go
new file mode 100644
index 0000000000..10153269b5
--- /dev/null
+++ b/pkg/cloudid/saml/providers/huawei/doc.go
@@ -0,0 +1,15 @@
+// Copyright 2019 Yunion
+//
+// 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 huawei // import "yunion.io/x/onecloud/pkg/cloudid/saml/providers/huawei"
diff --git a/pkg/cloudid/saml/providers/huawei/driver.go b/pkg/cloudid/saml/providers/huawei/driver.go
new file mode 100644
index 0000000000..ca773d1bff
--- /dev/null
+++ b/pkg/cloudid/saml/providers/huawei/driver.go
@@ -0,0 +1,49 @@
+// Copyright 2019 Yunion
+//
+// 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 huawei
+
+import (
+ "yunion.io/x/onecloud/pkg/httperrors"
+ "yunion.io/x/onecloud/pkg/util/samlutils"
+ "yunion.io/x/onecloud/pkg/util/samlutils/idp"
+)
+
+func (d *SHuaweiSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
+ // not supported
+ data := samlutils.SSAMLIdpInitiatedLoginData{}
+
+ return data, httperrors.ErrNotSupported
+}
+
+func (d *SHuaweiSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
+ // TODO
+ data := samlutils.SSAMLSpInitiatedLoginData{}
+
+ data.NameId = "yunionoss"
+ data.NameIdFormat = samlutils.NAME_ID_FORMAT_TRANSIENT
+ data.AudienceRestriction = sp.GetEntityId()
+ for k, v := range map[string]string{
+ "User": "ec2admin",
+ "Group": "ec2admin",
+ } {
+ data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{
+ Name: k, FriendlyName: k,
+ NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
+ Values: []string{v},
+ })
+ }
+
+ return data, nil
+}
diff --git a/pkg/cloudid/saml/providers/huawei/factory.go b/pkg/cloudid/saml/providers/huawei/factory.go
new file mode 100644
index 0000000000..83efab788f
--- /dev/null
+++ b/pkg/cloudid/saml/providers/huawei/factory.go
@@ -0,0 +1,38 @@
+// Copyright 2019 Yunion
+//
+// 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 huawei
+
+import (
+ "yunion.io/x/onecloud/pkg/cloudid/saml/providers"
+ "yunion.io/x/onecloud/pkg/cloudprovider"
+)
+
+type SHuaweiSAMLDriver struct{}
+
+func (d *SHuaweiSAMLDriver) GetEntityID() string {
+ return cloudprovider.SAML_ENTITY_ID_HUAWEI_CLOUD
+}
+
+func (d *SHuaweiSAMLDriver) GetMetadataFilename() string {
+ return "huawei.xml"
+}
+
+func (d *SHuaweiSAMLDriver) GetMetadataUrl() string {
+ return "https://auth.huaweicloud.com/authui/saml/metadata.xml"
+}
+
+func init() {
+ providers.Register(&SHuaweiSAMLDriver{})
+}
diff --git a/pkg/cloudid/saml/providers/qcloud/doc.go b/pkg/cloudid/saml/providers/qcloud/doc.go
new file mode 100644
index 0000000000..75eceb2ca6
--- /dev/null
+++ b/pkg/cloudid/saml/providers/qcloud/doc.go
@@ -0,0 +1,15 @@
+// Copyright 2019 Yunion
+//
+// 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 qcloud // import "yunion.io/x/onecloud/pkg/cloudid/saml/providers/qcloud"
diff --git a/pkg/cloudid/saml/providers/qcloud/driver.go b/pkg/cloudid/saml/providers/qcloud/driver.go
new file mode 100644
index 0000000000..f6ec7b7d17
--- /dev/null
+++ b/pkg/cloudid/saml/providers/qcloud/driver.go
@@ -0,0 +1,87 @@
+// Copyright 2019 Yunion
+//
+// 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 qcloud
+
+import (
+ "yunion.io/x/onecloud/pkg/util/samlutils"
+ "yunion.io/x/onecloud/pkg/util/samlutils/idp"
+)
+
+func (d *SQcloudSAMLDriver) GetIdpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error) {
+ // TODO
+ data := samlutils.SSAMLIdpInitiatedLoginData{}
+
+ data.NameId = "cvmcosreadonly"
+ data.NameIdFormat = samlutils.NAME_ID_FORMAT_TRANSIENT
+ data.AudienceRestriction = "https://cloud.tencent.com"
+ for _, v := range []struct {
+ name string
+ friendlyName string
+ value string
+ }{
+ {
+ name: "https://cloud.tencent.com/SAML/Attributes/Role",
+ friendlyName: "RoleEntitlement",
+ value: "qcs::cam::uin/100008182714:roleName/cvmcosreadonly,qcs::cam::uin/100008182714:saml-provider/saml.yunion.io",
+ },
+ {
+ name: "https://cloud.tencent.com/SAML/Attributes/RoleSessionName",
+ friendlyName: "RoleSessionName",
+ value: "cvmcosreadonly",
+ },
+ } {
+ data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{
+ Name: v.name,
+ FriendlyName: v.friendlyName,
+ Values: []string{v.value},
+ })
+ }
+ data.RelayState = "https://console.cloud.tencent.com/"
+
+ return data, nil
+}
+
+func (d *SQcloudSAMLDriver) GetSpInitiatedLoginData(cloudAccoutId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
+ // not supported
+ data := samlutils.SSAMLSpInitiatedLoginData{}
+
+ data.NameId = "cvmcosreadonly"
+ data.NameIdFormat = samlutils.NAME_ID_FORMAT_TRANSIENT
+ data.AudienceRestriction = "https://cloud.tencent.com"
+ for _, v := range []struct {
+ name string
+ friendlyName string
+ value string
+ }{
+ {
+ name: "https://cloud.tencent.com/SAML/Attributes/Role",
+ friendlyName: "RoleEntitlement",
+ value: "qcs::cam::uin/100008182714:roleName/cvmcosreadonly,qcs::cam::uin/100008182714:saml-provider/saml.yunion.io",
+ },
+ {
+ name: "https://cloud.tencent.com/SAML/Attributes/RoleSessionName",
+ friendlyName: "RoleSessionName",
+ value: "cvmcosreadonly",
+ },
+ } {
+ data.Attributes = append(data.Attributes, samlutils.SSAMLResponseAttribute{
+ Name: v.name,
+ FriendlyName: v.friendlyName,
+ Values: []string{v.value},
+ })
+ }
+
+ return data, nil
+}
diff --git a/pkg/cloudid/saml/providers/qcloud/factory.go b/pkg/cloudid/saml/providers/qcloud/factory.go
new file mode 100644
index 0000000000..a6df41cf0f
--- /dev/null
+++ b/pkg/cloudid/saml/providers/qcloud/factory.go
@@ -0,0 +1,38 @@
+// Copyright 2019 Yunion
+//
+// 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 qcloud
+
+import (
+ "yunion.io/x/onecloud/pkg/cloudid/saml/providers"
+ "yunion.io/x/onecloud/pkg/cloudprovider"
+)
+
+type SQcloudSAMLDriver struct{}
+
+func (d *SQcloudSAMLDriver) GetEntityID() string {
+ return cloudprovider.SAML_ENTITY_ID_QCLOUD
+}
+
+func (d *SQcloudSAMLDriver) GetMetadataFilename() string {
+ return "qcloud.xml"
+}
+
+func (d *SQcloudSAMLDriver) GetMetadataUrl() string {
+ return "http://cloud.tencent.com/saml.xml"
+}
+
+func init() {
+ providers.Register(&SQcloudSAMLDriver{})
+}
diff --git a/pkg/cloudid/saml/providers/register.go b/pkg/cloudid/saml/providers/register.go
new file mode 100644
index 0000000000..4ecbf79ef4
--- /dev/null
+++ b/pkg/cloudid/saml/providers/register.go
@@ -0,0 +1,34 @@
+// Copyright 2019 Yunion
+//
+// 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 providers
+
+var (
+ driverTable = make(map[string]ICloudSAMLLoginDriver)
+)
+
+func Register(driver ICloudSAMLLoginDriver) {
+ driverTable[driver.GetEntityID()] = driver
+}
+
+func FindDriver(entityId string) ICloudSAMLLoginDriver {
+ if driver, ok := driverTable[entityId]; ok {
+ return driver
+ }
+ return nil
+}
+
+func AllDrivers() map[string]ICloudSAMLLoginDriver {
+ return driverTable
+}
diff --git a/pkg/cloudid/saml/providers/types.go b/pkg/cloudid/saml/providers/types.go
new file mode 100644
index 0000000000..a7b9b4c29a
--- /dev/null
+++ b/pkg/cloudid/saml/providers/types.go
@@ -0,0 +1,30 @@
+// Copyright 2019 Yunion
+//
+// 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 providers
+
+import (
+ "yunion.io/x/onecloud/pkg/util/samlutils"
+ "yunion.io/x/onecloud/pkg/util/samlutils/idp"
+)
+
+type ICloudSAMLLoginDriver interface {
+ GetEntityID() string
+
+ GetMetadataFilename() string
+ GetMetadataUrl() string
+
+ GetIdpInitiatedLoginData(idpId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLIdpInitiatedLoginData, error)
+ GetSpInitiatedLoginData(idpId string, userId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error)
+}
diff --git a/pkg/cloudid/service/doc.go b/pkg/cloudid/service/doc.go
index de28f13e34..0f386160de 100644
--- a/pkg/cloudid/service/doc.go
+++ b/pkg/cloudid/service/doc.go
@@ -1 +1,15 @@
+// Copyright 2019 Yunion
+//
+// 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 service // import "yunion.io/x/onecloud/pkg/cloudid/service"
diff --git a/pkg/cloudid/service/handlers.go b/pkg/cloudid/service/handlers.go
index 0bd8361e6e..fb60fdaf46 100644
--- a/pkg/cloudid/service/handlers.go
+++ b/pkg/cloudid/service/handlers.go
@@ -1,3 +1,17 @@
+// Copyright 2019 Yunion
+//
+// 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 service
// Copyright 2019 Yunion
diff --git a/pkg/cloudid/service/service.go b/pkg/cloudid/service/service.go
index 8dddfe4009..5b3d28ddd0 100644
--- a/pkg/cloudid/service/service.go
+++ b/pkg/cloudid/service/service.go
@@ -20,6 +20,7 @@ import (
"yunion.io/x/log"
+ "yunion.io/x/onecloud/pkg/apis/cloudid"
"yunion.io/x/onecloud/pkg/cloudcommon"
common_app "yunion.io/x/onecloud/pkg/cloudcommon/app"
"yunion.io/x/onecloud/pkg/cloudcommon/cronman"
@@ -28,6 +29,7 @@ import (
"yunion.io/x/onecloud/pkg/cloudid/models"
"yunion.io/x/onecloud/pkg/cloudid/options"
_ "yunion.io/x/onecloud/pkg/cloudid/policy"
+ "yunion.io/x/onecloud/pkg/cloudid/saml"
_ "yunion.io/x/onecloud/pkg/cloudid/tasks"
_ "yunion.io/x/onecloud/pkg/multicloud/loader"
)
@@ -37,7 +39,7 @@ func StartService() {
dbOpts := &opts.DBOptions
baseOpts := &opts.BaseOptions
commonOpts := &opts.CommonOptions
- common_options.ParseOptions(opts, os.Args, "cloudid.conf", "cloudid")
+ common_options.ParseOptions(opts, os.Args, "cloudid.conf", cloudid.SERVICE_TYPE)
common_app.InitAuth(commonOpts, func() {
log.Infof("Auth complete!!")
@@ -49,6 +51,12 @@ func StartService() {
db.EnsureAppInitSyncDB(app, dbOpts, models.InitDB)
defer cloudcommon.CloseDB()
+ err := saml.InitSAML(app, cloudid.SAML_IDP_PREFIX)
+ if err != nil {
+ log.Errorf("SAML initialization fail %s", err)
+ return
+ }
+
if !opts.IsSlaveNode {
cron := cronman.InitCronJobManager(true, options.Options.CronJobWorkerCount)
cron.AddJobAtIntervalsWithStartRun("SyncCloudaccounts", time.Duration(opts.CloudaccountSyncIntervalMinutes)*time.Minute, models.CloudaccountManager.SyncCloudaccounts, true)
diff --git a/pkg/cloudid/tasks/doc.go b/pkg/cloudid/tasks/doc.go
index 715bb89783..5df2a8700d 100644
--- a/pkg/cloudid/tasks/doc.go
+++ b/pkg/cloudid/tasks/doc.go
@@ -1 +1,15 @@
+// Copyright 2019 Yunion
+//
+// 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 tasks // import "yunion.io/x/onecloud/pkg/cloudid/tasks"
diff --git a/pkg/cloudprovider/cloudprovider.go b/pkg/cloudprovider/cloudprovider.go
index b6e51396fa..4341521ffd 100644
--- a/pkg/cloudprovider/cloudprovider.go
+++ b/pkg/cloudprovider/cloudprovider.go
@@ -247,6 +247,9 @@ type ICloudProvider interface {
GetEnrollmentAccounts() ([]SEnrollmentAccount, error)
CreateSubscription(SubscriptionCreateInput) error
+
+ GetSamlEntityId() string
+ GetSamlSpInitiatedLoginUrl(idpName string) string
}
func IsSupportProject(prod ICloudProvider) bool {
@@ -409,6 +412,14 @@ func (self *SBaseProvider) CreateIProject(name string) (ICloudProject, error) {
return nil, ErrNotImplemented
}
+func (self *SBaseProvider) GetSamlEntityId() string {
+ return ""
+}
+
+func (self *SBaseProvider) GetSamlSpInitiatedLoginUrl(idpName string) string {
+ return ""
+}
+
func NewBaseProvider(factory ICloudProviderFactory) SBaseProvider {
return SBaseProvider{factory: factory}
}
diff --git a/pkg/cloudprovider/saml.go b/pkg/cloudprovider/saml.go
new file mode 100644
index 0000000000..30012203d7
--- /dev/null
+++ b/pkg/cloudprovider/saml.go
@@ -0,0 +1,24 @@
+// Copyright 2019 Yunion
+//
+// 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 cloudprovider
+
+const (
+ SAML_ENTITY_ID_ALIYUN_ROLE = "urn:alibaba:cloudcomputing"
+ SAML_ENTITY_ID_AWS_CN = "urn:amazon:webservices:cn-north-1"
+ SAML_ENTITY_ID_AWS = "urn:amazon:webservices"
+ SAML_ENTITY_ID_QCLOUD = "cloud.tencent.com"
+ SAML_ENTITY_ID_HUAWEI_CLOUD = "https://auth.huaweicloud.com/"
+ SAML_ENTITY_ID_GOOGLE = "google.com"
+)
diff --git a/pkg/compute/models/cloudaccount_saml.go b/pkg/compute/models/cloudaccount_saml.go
new file mode 100644
index 0000000000..0a623990bd
--- /dev/null
+++ b/pkg/compute/models/cloudaccount_saml.go
@@ -0,0 +1,67 @@
+// Copyright 2019 Yunion
+//
+// 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 models
+
+import (
+ "context"
+ "fmt"
+
+ "yunion.io/x/jsonutils"
+ "yunion.io/x/pkg/errors"
+
+ "yunion.io/x/onecloud/pkg/apis/cloudid"
+ api "yunion.io/x/onecloud/pkg/apis/compute"
+ "yunion.io/x/onecloud/pkg/cloudcommon/db"
+ "yunion.io/x/onecloud/pkg/compute/options"
+ "yunion.io/x/onecloud/pkg/httperrors"
+ "yunion.io/x/onecloud/pkg/mcclient"
+ "yunion.io/x/onecloud/pkg/util/httputils"
+ "yunion.io/x/onecloud/pkg/util/samlutils"
+)
+
+func (account *SCloudaccount) AllowGetDetailsSaml(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
+ return db.IsDomainAllowGetSpec(userCred, account, "saml")
+}
+
+func (account *SCloudaccount) GetDetailsSaml(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (api.GetCloudaccountSamlOutput, error) {
+ output := api.GetCloudaccountSamlOutput{}
+
+ provider, err := account.GetProvider()
+ if err != nil {
+ return output, errors.Wrap(err, "GetProviderFactory")
+ }
+
+ output.EntityId = provider.GetSamlEntityId()
+ if len(output.EntityId) == 0 {
+ return output, errors.Wrap(httperrors.ErrNotSupported, "SAML login not supported")
+ }
+ output.RedirectLoginUrl = httputils.JoinPath(options.Options.ApiServer, cloudid.SAML_IDP_PREFIX, "redirect/login", account.Id)
+ output.RedirectLogoutUrl = httputils.JoinPath(options.Options.ApiServer, cloudid.SAML_IDP_PREFIX, "redirect/logout", account.Id)
+ output.MetadataUrl = httputils.JoinPath(options.Options.ApiServer, cloudid.SAML_IDP_PREFIX, "metadata", account.Id)
+ // XXXXX
+ // TODO, find idpName for this cloudaccount
+ // XXXXX
+ idpName := "saml.yunion.io"
+ output.InitLoginUrl = provider.GetSamlSpInitiatedLoginUrl(idpName)
+ if len(output.InitLoginUrl) == 0 {
+ input := samlutils.SIdpInitiatedLoginInput{
+ EntityID: output.EntityId,
+ IdpId: account.Id,
+ }
+ output.InitLoginUrl = httputils.JoinPath(options.Options.ApiServer, cloudid.SAML_IDP_PREFIX, fmt.Sprintf("sso?%s", jsonutils.Marshal(input).QueryString()))
+ }
+
+ return output, nil
+}
diff --git a/pkg/mcclient/auth/middleware.go b/pkg/mcclient/auth/middleware.go
index d22c841dfd..a5d914a362 100644
--- a/pkg/mcclient/auth/middleware.go
+++ b/pkg/mcclient/auth/middleware.go
@@ -36,7 +36,7 @@ var (
)
const (
- AUTH_TOKEN = appctx.AppContextKey("X_AUTH_TOKEN")
+ AUTH_TOKEN = appctx.APP_CONTEXT_KEY_AUTH_TOKEN
)
type TokenVerifyFunc func(string) (mcclient.TokenCredential, error)
@@ -67,7 +67,7 @@ func AuthenticateWithDelayDecision(f appsrv.FilterHandler, delayDecision bool) a
}
}
}
- ctx = context.WithValue(ctx, AUTH_TOKEN, token)
+ ctx = context.WithValue(ctx, appctx.APP_CONTEXT_KEY_AUTH_TOKEN, token)
if taskId := r.Header.Get(mcclient.TASK_ID); taskId != "" {
ctx = context.WithValue(ctx, appctx.APP_CONTEXT_KEY_TASK_ID, taskId)
@@ -81,7 +81,7 @@ func AuthenticateWithDelayDecision(f appsrv.FilterHandler, delayDecision bool) a
}
func FetchUserCredential(ctx context.Context, filter func(mcclient.TokenCredential) mcclient.TokenCredential) mcclient.TokenCredential {
- tokenValue := ctx.Value(AUTH_TOKEN)
+ tokenValue := ctx.Value(appctx.APP_CONTEXT_KEY_AUTH_TOKEN)
if tokenValue != nil {
token := tokenValue.(mcclient.TokenCredential)
if filter != nil {
diff --git a/pkg/monitor/service/dispatcher.go b/pkg/monitor/service/dispatcher.go
index c1bda03de5..e2fa37fae2 100644
--- a/pkg/monitor/service/dispatcher.go
+++ b/pkg/monitor/service/dispatcher.go
@@ -39,7 +39,7 @@ func performHandler(ctx context.Context, w http.ResponseWriter, r *http.Request)
_, query, body := fetchEnv(ctx, w, r)
appsrv.SendJSON(w, wrap(jsonutils.NewDict(), "subscription"))
SubscriptionWorkerManager.Run(func() {
- ctx = context.WithValue(context.Background(), auth.AUTH_TOKEN, auth.AdminCredential())
+ ctx = context.WithValue(context.Background(), appctx.APP_CONTEXT_KEY_AUTH_TOKEN, auth.AdminCredential())
subscriptionmodel.SubscriptionManager.PerformWrite(ctx, auth.AdminCredential(), query, body)
}, nil, nil)
diff --git a/pkg/multicloud/aliyun/provider/provider.go b/pkg/multicloud/aliyun/provider/provider.go
index eed415378c..866b5f7901 100644
--- a/pkg/multicloud/aliyun/provider/provider.go
+++ b/pkg/multicloud/aliyun/provider/provider.go
@@ -228,3 +228,11 @@ func (self *SAliyunProvider) GetICustomCloudpolicies() ([]cloudprovider.ICloudpo
func (self *SAliyunProvider) CreateICloudpolicy(opts *cloudprovider.SCloudpolicyCreateOptions) (cloudprovider.ICloudpolicy, error) {
return self.client.CreateICloudpolicy(opts)
}
+
+func (self *SAliyunProvider) GetSamlEntityId() string {
+ return cloudprovider.SAML_ENTITY_ID_ALIYUN_ROLE
+}
+
+func (self *SAliyunProvider) GetSamlSpInitiatedLoginUrl(idpName string) string {
+ return ""
+}
diff --git a/pkg/multicloud/aws/aws.go b/pkg/multicloud/aws/aws.go
index fce1cac503..9929159c36 100644
--- a/pkg/multicloud/aws/aws.go
+++ b/pkg/multicloud/aws/aws.go
@@ -568,3 +568,12 @@ func (client *SAwsClient) GetObjectCannedAcls() []string {
}
}
}
+
+func (client *SAwsClient) GetSamlEntityId() string {
+ switch client.accessUrl {
+ case AWS_CHINA_CLOUDENV:
+ return cloudprovider.SAML_ENTITY_ID_AWS_CN
+ default:
+ return cloudprovider.SAML_ENTITY_ID_AWS
+ }
+}
diff --git a/pkg/multicloud/aws/provider/provider.go b/pkg/multicloud/aws/provider/provider.go
index 80eb78f983..ab14e16805 100644
--- a/pkg/multicloud/aws/provider/provider.go
+++ b/pkg/multicloud/aws/provider/provider.go
@@ -219,3 +219,11 @@ func (self *SAwsProvider) GetIClouduserByName(name string) (cloudprovider.ICloud
func (self *SAwsProvider) CreateICloudpolicy(opts *cloudprovider.SCloudpolicyCreateOptions) (cloudprovider.ICloudpolicy, error) {
return self.client.CreateICloudpolicy(opts)
}
+
+func (self *SAwsProvider) GetSamlEntityId() string {
+ return self.client.GetSamlEntityId()
+}
+
+func (self *SAwsProvider) GetSamlSpInitiatedLoginUrl(idpName string) string {
+ return ""
+}
diff --git a/pkg/multicloud/google/google.go b/pkg/multicloud/google/google.go
index 6fe3352e29..88da05af29 100644
--- a/pkg/multicloud/google/google.go
+++ b/pkg/multicloud/google/google.go
@@ -911,3 +911,20 @@ func (self *SGoogleClient) GetCapabilities() []string {
}
return caps
}
+
+func (self *SGoogleClient) GetSamlSpInitiatedLoginUrl(idpName string) string {
+ // GOOGLE只支持一个IDP, 可以将organization名字存储在idpName里,避免因为权限不足,无法获取organization名称
+ if len(idpName) == 0 {
+ orgs, _ := self.ListOrganizations()
+ if len(orgs) != 1 {
+ log.Warningf("Organization count %d != 1, require assign the service account to ONE organization with organization viewer/admin role", len(orgs))
+ } else {
+ idpName = orgs[0].DisplayName
+ }
+ }
+ if len(idpName) == 0 {
+ log.Errorf("no valid organization name for this GCP account")
+ return ""
+ }
+ return fmt.Sprintf("https://www.google.com/a/%s/ServiceLogin?continue=https://console.cloud.google.com", idpName)
+}
diff --git a/pkg/multicloud/google/provider/provider.go b/pkg/multicloud/google/provider/provider.go
index c44aef5cdd..196690d984 100644
--- a/pkg/multicloud/google/provider/provider.go
+++ b/pkg/multicloud/google/provider/provider.go
@@ -293,3 +293,11 @@ func (self *SGoogleProvider) GetIClouduserByName(name string) (cloudprovider.ICl
func (self *SGoogleProvider) CreateICloudpolicy(opts *cloudprovider.SCloudpolicyCreateOptions) (cloudprovider.ICloudpolicy, error) {
return self.client.CreateICloudpolicy(opts)
}
+
+func (self *SGoogleProvider) GetSamlEntityId() string {
+ return cloudprovider.SAML_ENTITY_ID_GOOGLE
+}
+
+func (self *SGoogleProvider) GetSamlSpInitiatedLoginUrl(idpName string) string {
+ return self.client.GetSamlSpInitiatedLoginUrl(idpName)
+}
diff --git a/pkg/multicloud/google/saml.go b/pkg/multicloud/google/saml.go
new file mode 100644
index 0000000000..7b311e7f66
--- /dev/null
+++ b/pkg/multicloud/google/saml.go
@@ -0,0 +1,55 @@
+// Copyright 2019 Yunion
+//
+// 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 google
+
+import (
+ "time"
+
+ "yunion.io/x/log"
+ "yunion.io/x/pkg/errors"
+)
+
+type SOrganizationOwner struct {
+ DirectoryCustomerId string `json:"directoryCustomerId"`
+}
+
+type SOrganization struct {
+ OrganizationId string `json:"organizationId"`
+ DisplayName string `json:"displayName"`
+ Owner SOrganizationOwner `json:"owner"`
+ CreationTime time.Time `json:"creationTime"`
+ LifecycleState string `json:"lifecycleState"`
+ Name string `json:"name"`
+}
+
+// https://cloud.google.com/resource-manager/reference/rest/v1/organizations/search
+// require Organization Viewer privilege
+func (self *SGoogleClient) ListOrganizations() ([]SOrganization, error) {
+ resource := "organizations"
+ params := map[string]string{
+ "pageSize": "1000",
+ }
+ resp, err := jsonRequest(self.client, "GET", GOOGLE_MANAGER_DOMAIN, "v1beta1", resource, params, nil, self.debug)
+ if err != nil {
+ return nil, errors.Wrap(err, "ListOrganizations")
+ }
+ log.Debugf("ListOrganization: %s", resp)
+ ret := make([]SOrganization, 0)
+ err = resp.Unmarshal(&ret, "organizations")
+ if err != nil {
+ return nil, errors.Wrap(err, "resp.Unmarshal")
+ }
+ return ret, nil
+}
diff --git a/pkg/multicloud/google/shell/resourcepolicy.go b/pkg/multicloud/google/shell/resourcepolicy.go
index 39b30714b2..3d6ee48c0c 100644
--- a/pkg/multicloud/google/shell/resourcepolicy.go
+++ b/pkg/multicloud/google/shell/resourcepolicy.go
@@ -46,4 +46,14 @@ func init() {
return nil
})
+ type ListOrganizationOptions struct{}
+ shellutils.R(&ListOrganizationOptions{}, "organization-list", "List organizaitons", func(cli *google.SRegion, args *ListOrganizationOptions) error {
+ orgs, err := cli.GetClient().ListOrganizations()
+ if err != nil {
+ return err
+ }
+ printList(orgs, 0, 0, 0, nil)
+ return nil
+ })
+
}
diff --git a/pkg/multicloud/huawei/huawei.go b/pkg/multicloud/huawei/huawei.go
index 775b5de54b..e39c088c39 100644
--- a/pkg/multicloud/huawei/huawei.go
+++ b/pkg/multicloud/huawei/huawei.go
@@ -542,3 +542,7 @@ func (self *SHuaweiClient) initOwner() error {
self.ownerId = ownerId
return nil
}
+
+func (self *SHuaweiClient) GetSamlSpInitiatedLoginUrl(idpName string) string {
+ return fmt.Sprintf("https://auth.huaweicloud.com/authui/federation/websso?domain_id=%s&idp=%s&protocol=saml", self.ownerId, idpName)
+}
diff --git a/pkg/multicloud/huawei/provider/provider.go b/pkg/multicloud/huawei/provider/provider.go
index 06b372cf7f..50ddca92bb 100644
--- a/pkg/multicloud/huawei/provider/provider.go
+++ b/pkg/multicloud/huawei/provider/provider.go
@@ -263,3 +263,11 @@ func (self *SHuaweiProvider) GetICustomCloudpolicies() ([]cloudprovider.ICloudpo
func (self *SHuaweiProvider) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) {
return self.client.GetIClouduserByName(name)
}
+
+func (self *SHuaweiProvider) GetSamlEntityId() string {
+ return cloudprovider.SAML_ENTITY_ID_HUAWEI_CLOUD
+}
+
+func (self *SHuaweiProvider) GetSamlSpInitiatedLoginUrl(idpName string) string {
+ return self.client.GetSamlSpInitiatedLoginUrl(idpName)
+}
diff --git a/pkg/multicloud/qcloud/provider/provider.go b/pkg/multicloud/qcloud/provider/provider.go
index 6f459c9d36..587785ee56 100644
--- a/pkg/multicloud/qcloud/provider/provider.go
+++ b/pkg/multicloud/qcloud/provider/provider.go
@@ -265,3 +265,11 @@ func (self *SQcloudProvider) GetIClouduserByName(name string) (cloudprovider.ICl
func (self *SQcloudProvider) CreateICloudpolicy(opts *cloudprovider.SCloudpolicyCreateOptions) (cloudprovider.ICloudpolicy, error) {
return self.client.CreateICloudpolicy(opts)
}
+
+func (self *SQcloudProvider) GetSamlEntityId() string {
+ return cloudprovider.SAML_ENTITY_ID_QCLOUD
+}
+
+func (self *SQcloudProvider) GetSamlSpInitiatedLoginUrl(idpName string) string {
+ return self.client.GetSamlSpInitiatedLoginUrl(idpName)
+}
diff --git a/pkg/multicloud/qcloud/qcloud.go b/pkg/multicloud/qcloud/qcloud.go
index eb991105cd..aef305b9a6 100644
--- a/pkg/multicloud/qcloud/qcloud.go
+++ b/pkg/multicloud/qcloud/qcloud.go
@@ -814,3 +814,7 @@ func (self *SQcloudClient) GetCapabilities() []string {
}
return caps
}
+
+func (self *SQcloudClient) GetSamlSpInitiatedLoginUrl(idpName string) string {
+ return fmt.Sprintf("https://cloud.tencent.com/login/forwardIdp/%s/%s", self.ownerName, idpName)
+}
diff --git a/pkg/proxy/http.go b/pkg/proxy/http.go
index 41c16d2a03..1c22d4e680 100644
--- a/pkg/proxy/http.go
+++ b/pkg/proxy/http.go
@@ -26,7 +26,8 @@ import (
"yunion.io/x/onecloud/pkg/httperrors"
)
-type EndpointGenerator func(context.Context, http.ResponseWriter, *http.Request) (string, error)
+type EndpointGenerator func(context.Context, *http.Request) (string, error)
+type RequestManipulator func(ctx context.Context, r *http.Request) (*http.Request, error)
type SEndpointFactory struct {
generator EndpointGenerator
@@ -42,16 +43,18 @@ func NewEndpointFactory(f EndpointGenerator, serviceName string) *SEndpointFacto
type SReverseProxy struct {
*SEndpointFactory
+ manipulator RequestManipulator
}
-func NewHTTPReverseProxy(ef *SEndpointFactory) *SReverseProxy {
+func NewHTTPReverseProxy(ef *SEndpointFactory, m RequestManipulator) *SReverseProxy {
return &SReverseProxy{
SEndpointFactory: ef,
+ manipulator: m,
}
}
func (p *SReverseProxy) ServeHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request) {
- endpoint, err := p.generator(ctx, w, r)
+ endpoint, err := p.generator(ctx, r)
if err != nil {
httperrors.InternalServerError(w, err.Error())
return
@@ -68,7 +71,10 @@ func (p *SReverseProxy) ServeHTTP(ctx context.Context, w http.ResponseWriter, r
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
DisableKeepAlives: true,
}
- r.Header.Del("Cookie")
- r.Header.Del("X-Auth-Token")
+ r, err = p.manipulator(ctx, r)
+ if err != nil {
+ httperrors.InternalServerError(w, err.Error())
+ return
+ }
proxy.ServeHTTP(w, r)
}
diff --git a/pkg/s3gateway/handlers/middelware.go b/pkg/s3gateway/handlers/middelware.go
index b703eda693..85f2d5a943 100644
--- a/pkg/s3gateway/handlers/middelware.go
+++ b/pkg/s3gateway/handlers/middelware.go
@@ -45,7 +45,7 @@ func s3authenticate(f appsrv.FilterHandler) appsrv.FilterHandler {
SendError(w, Unauthenticated(ctx, err.Error()))
return
}
- ctx = context.WithValue(ctx, auth.AUTH_TOKEN, userCred)
+ ctx = context.WithValue(ctx, appctx.APP_CONTEXT_KEY_AUTH_TOKEN, userCred)
f(ctx, w, r)
}
diff --git a/pkg/util/httputils/httputils.go b/pkg/util/httputils/httputils.go
index 4c027846fa..a78a06d7d0 100644
--- a/pkg/util/httputils/httputils.go
+++ b/pkg/util/httputils/httputils.go
@@ -786,6 +786,12 @@ func ParseJSONResponse(reqBody string, resp *http.Response, err error, debug boo
}
}
-func JoinPath(ep string, path string) string {
- return strings.TrimRight(ep, "/") + "/" + strings.TrimLeft(path, "/")
+func JoinPath(ep string, paths ...string) string {
+ buf := strings.Builder{}
+ buf.WriteString(strings.TrimRight(ep, "/"))
+ for _, path := range paths {
+ buf.WriteByte('/')
+ buf.WriteString(strings.Trim(path, "/"))
+ }
+ return buf.String()
}
diff --git a/pkg/util/samlutils/demo/service.go b/pkg/util/samlutils/demo/service.go
index 823fd480dd..3296aab511 100644
--- a/pkg/util/samlutils/demo/service.go
+++ b/pkg/util/samlutils/demo/service.go
@@ -22,6 +22,7 @@ import (
"os"
"strings"
+ "yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/structarg"
@@ -41,6 +42,7 @@ type Options struct {
Key string `help:"certificate private key file"`
Port int `help:"listening port"`
Entity string `help:"SAML entityID"`
+ IdpId string `help:"IDP ID"`
SpMeta []string `help:"ServiceProvider metadata filename"`
IdpMeta []string `help:"IdentityProvider metadata filename"`
}
@@ -105,7 +107,7 @@ func prepareServer() error {
return errors.Wrap(err, "NewSAMLInstance")
}
- spFunc := func(ctx context.Context, sp *idp.SSAMLServiceProvider) samlutils.SSAMLSpInitiatedLoginData {
+ spFunc := func(ctx context.Context, idpId string, sp *idp.SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error) {
log.Debugf("Recive SP initiated Login: %s", sp.GetEntityId())
data := samlutils.SSAMLSpInitiatedLoginData{}
switch sp.GetEntityId() {
@@ -219,10 +221,10 @@ func prepareServer() error {
})
}
}
- return data
+ return data, nil
}
- idpFunc := func(ctx context.Context, sp *idp.SSAMLServiceProvider, state string) samlutils.SSAMLIdpInitiatedLoginData {
+ idpFunc := func(ctx context.Context, sp *idp.SSAMLServiceProvider, idpId string) (samlutils.SSAMLIdpInitiatedLoginData, error) {
log.Debugf("Recive IDP initiated Login: %s", sp.GetEntityId())
data := samlutils.SSAMLIdpInitiatedLoginData{}
switch sp.GetEntityId() {
@@ -333,10 +335,10 @@ func prepareServer() error {
}
data.RelayState = "https://console.cloud.tencent.com/"
}
- return data
+ return data, nil
}
- logoutFunc := func(ctx context.Context) string {
+ logoutFunc := func(ctx context.Context, idpId string) string {
return fmt.Sprintf(``, httputils.JoinPath(options.Entity, "SAML/idp"))
}
@@ -347,7 +349,7 @@ func prepareServer() error {
return errors.Wrapf(err, "AddSPMetadataFile %s", spMetaFile)
}
}
- idpInst.AddHandlers(app, "SAML/idp")
+ idpInst.AddHandlers(app, "SAML/idp", nil)
idpInst.SetHtmlTemplate(`正在跳转到云控制台,请等待。。。
$FORM$`)
app.AddHandler("GET", "SAML/idp", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
@@ -377,7 +379,11 @@ func prepareServer() error {
entityID: "cloud.tencent.com",
},
} {
- htmlBuf.WriteString(fmt.Sprintf(`%s (IDP-Initiated)`, idpInitUrl, url.QueryEscape(v.entityID), v.name))
+ query := samlutils.SIdpInitiatedLoginInput{
+ EntityID: v.entityID,
+ IdpId: options.IdpId,
+ }
+ htmlBuf.WriteString(fmt.Sprintf(`%s (IDP-Initiated)`, idpInitUrl, jsonutils.Marshal(query).QueryString(), v.name))
}
for _, v := range []struct {
diff --git a/pkg/util/samlutils/idp/idp.go b/pkg/util/samlutils/idp/idp.go
index e8f0cf8fc1..2133190d16 100644
--- a/pkg/util/samlutils/idp/idp.go
+++ b/pkg/util/samlutils/idp/idp.go
@@ -25,15 +25,20 @@ import (
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
+ "yunion.io/x/onecloud/pkg/appctx"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/util/httputils"
"yunion.io/x/onecloud/pkg/util/samlutils"
)
-type OnSpInitiatedLogin func(ctx context.Context, sp *SSAMLServiceProvider) samlutils.SSAMLSpInitiatedLoginData
-type OnIdpInitiatedLogin func(ctx context.Context, sp *SSAMLServiceProvider, state string) samlutils.SSAMLIdpInitiatedLoginData
-type OnLogout func(ctx context.Context) string
+const (
+ IDP_ID_KEY = ""
+)
+
+type OnSpInitiatedLogin func(ctx context.Context, idpId string, sp *SSAMLServiceProvider) (samlutils.SSAMLSpInitiatedLoginData, error)
+type OnIdpInitiatedLogin func(ctx context.Context, sp *SSAMLServiceProvider, IdpId string) (samlutils.SSAMLIdpInitiatedLoginData, error)
+type OnLogout func(ctx context.Context, idpId string) string
type SSAMLIdpInstance struct {
saml *samlutils.SSAMLInstance
@@ -61,20 +66,28 @@ func NewIdpInstance(saml *samlutils.SSAMLInstance, spLoginFunc OnSpInitiatedLogi
}
}
-func (idp *SSAMLIdpInstance) AddHandlers(app *appsrv.Application, prefix string) {
- idp.metadataPath = httputils.JoinPath(prefix, "metadata")
- idp.redirectLoginPath = httputils.JoinPath(prefix, "redirect/login")
- idp.redirectLogoutPath = httputils.JoinPath(prefix, "redirect/logout")
+func (idp *SSAMLIdpInstance) AddHandlers(app *appsrv.Application, prefix string, middleware appsrv.TMiddleware) {
+ idp.metadataPath = httputils.JoinPath(prefix, "metadata/"+IDP_ID_KEY)
+ idp.redirectLoginPath = httputils.JoinPath(prefix, "redirect/login/"+IDP_ID_KEY)
+ idp.redirectLogoutPath = httputils.JoinPath(prefix, "redirect/logout/"+IDP_ID_KEY)
idp.idpInitiatedSSOPath = httputils.JoinPath(prefix, "sso")
app.AddHandler("GET", idp.metadataPath, idp.metadataHandler)
- app.AddHandler("GET", idp.redirectLoginPath, idp.redirectLoginHandler)
- app.AddHandler("GET", idp.redirectLogoutPath, idp.redirectLogoutHandler)
+ handler := idp.redirectLoginHandler
+ if middleware != nil {
+ handler = middleware(handler)
+ }
+ app.AddHandler("GET", idp.redirectLoginPath, handler)
+ handler = idp.redirectLogoutHandler
+ if middleware != nil {
+ handler = middleware(handler)
+ }
+ app.AddHandler("GET", idp.redirectLogoutPath, handler)
app.AddHandler("GET", idp.idpInitiatedSSOPath, idp.idpInitiatedSSOHandler)
- log.Infof("IDP metadata: %s", idp.getMetadataUrl())
- log.Infof("IDP redirect login: %s", idp.getRedirectLoginUrl())
- log.Infof("IDP redirect logout: %s", idp.getRedirectLogoutUrl())
+ log.Infof("IDP metadata: %s", idp.getMetadataUrl(IDP_ID_KEY))
+ log.Infof("IDP redirect login: %s", idp.getRedirectLoginUrl(IDP_ID_KEY))
+ log.Infof("IDP redirect logout: %s", idp.getRedirectLogoutUrl(IDP_ID_KEY))
log.Infof("IDP initated SSO: %s", idp.getIdpInitiatedSSOUrl())
}
@@ -109,16 +122,16 @@ func (idp *SSAMLIdpInstance) AddSPMetadata(metadata []byte) error {
return nil
}
-func (idp *SSAMLIdpInstance) getMetadataUrl() string {
- return httputils.JoinPath(idp.saml.GetEntityId(), idp.metadataPath)
+func (idp *SSAMLIdpInstance) getMetadataUrl(idpId string) string {
+ return strings.Replace(httputils.JoinPath(idp.saml.GetEntityId(), idp.metadataPath), IDP_ID_KEY, idpId, 1)
}
-func (idp *SSAMLIdpInstance) getRedirectLoginUrl() string {
- return httputils.JoinPath(idp.saml.GetEntityId(), idp.redirectLoginPath)
+func (idp *SSAMLIdpInstance) getRedirectLoginUrl(idpId string) string {
+ return strings.Replace(httputils.JoinPath(idp.saml.GetEntityId(), idp.redirectLoginPath), IDP_ID_KEY, idpId, 1)
}
-func (idp *SSAMLIdpInstance) getRedirectLogoutUrl() string {
- return httputils.JoinPath(idp.saml.GetEntityId(), idp.redirectLogoutPath)
+func (idp *SSAMLIdpInstance) getRedirectLogoutUrl(idpId string) string {
+ return strings.Replace(httputils.JoinPath(idp.saml.GetEntityId(), idp.redirectLogoutPath), IDP_ID_KEY, idpId, 1)
}
func (idp *SSAMLIdpInstance) getIdpInitiatedSSOUrl() string {
@@ -126,12 +139,15 @@ func (idp *SSAMLIdpInstance) getIdpInitiatedSSOUrl() string {
}
func (idp *SSAMLIdpInstance) metadataHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
- desc := idp.getMetadata(ctx)
+ params := appctx.AppContextParams(ctx)
+ idpId := params[IDP_ID_KEY]
+ desc := idp.getMetadata(idpId)
appsrv.SendXmlWithIndent(w, nil, desc, true)
}
func (idp *SSAMLIdpInstance) redirectLoginHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
- _, query, _ := appsrv.FetchEnv(ctx, w, r)
+ params, query, _ := appsrv.FetchEnv(ctx, w, r)
+ idpId := params[IDP_ID_KEY]
input := samlutils.SIdpRedirectLoginInput{}
err := query.Unmarshal(&input)
if err != nil {
@@ -139,7 +155,7 @@ func (idp *SSAMLIdpInstance) redirectLoginHandler(ctx context.Context, w http.Re
return
}
log.Debugf("recv input %s", input)
- respHtml, err := idp.processLoginRequest(ctx, input)
+ respHtml, err := idp.processLoginRequest(ctx, idpId, input)
if err != nil {
httperrors.InputParameterError(w, "parse parameter error %s", err)
return
@@ -148,8 +164,10 @@ func (idp *SSAMLIdpInstance) redirectLoginHandler(ctx context.Context, w http.Re
}
func (idp *SSAMLIdpInstance) redirectLogoutHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
+ params := appctx.AppContextParams(ctx)
+ idpId := params[IDP_ID_KEY]
log.Debugf("logout: %s", r.Header)
- html := idp.onLogout(ctx)
+ html := idp.onLogout(ctx, idpId)
appsrv.SendHTML(w, html)
}
@@ -169,17 +187,17 @@ func (idp *SSAMLIdpInstance) idpInitiatedSSOHandler(ctx context.Context, w http.
appsrv.SendHTML(w, respHtml)
}
-func (idp *SSAMLIdpInstance) getMetadata(ctx context.Context) samlutils.EntityDescriptor {
+func (idp *SSAMLIdpInstance) getMetadata(idpId string) samlutils.EntityDescriptor {
input := samlutils.SSAMLIdpMetadataInput{
EntityId: idp.saml.GetEntityId(),
CertString: idp.saml.GetCertString(),
- RedirectLoginUrl: idp.getRedirectLoginUrl(),
- RedirectLogoutUrl: idp.getRedirectLogoutUrl(),
+ RedirectLoginUrl: idp.getRedirectLoginUrl(idpId),
+ RedirectLogoutUrl: idp.getRedirectLogoutUrl(idpId),
}
return samlutils.NewIdpMetadata(input)
}
-func (idp *SSAMLIdpInstance) processLoginRequest(ctx context.Context, input samlutils.SIdpRedirectLoginInput) (string, error) {
+func (idp *SSAMLIdpInstance) processLoginRequest(ctx context.Context, idpId string, input samlutils.SIdpRedirectLoginInput) (string, error) {
plainText, err := samlutils.SAMLDecode(input.SAMLRequest)
if err != nil {
return "", errors.Wrap(err, "samlutils.SAMLDecode")
@@ -198,15 +216,15 @@ func (idp *SSAMLIdpInstance) processLoginRequest(ctx context.Context, input saml
return "", errors.Wrapf(httperrors.ErrResourceNotFound, "issuer %s not found", authReq.Issuer.Issuer)
}
- if len(authReq.Destination) > 0 && authReq.Destination != idp.getRedirectLoginUrl() {
- return "", errors.Wrapf(httperrors.ErrInputParameter, "Destination not match: get %s want %s", authReq.Destination, idp.getRedirectLoginUrl())
+ if len(authReq.Destination) > 0 && authReq.Destination != idp.getRedirectLoginUrl(idpId) {
+ return "", errors.Wrapf(httperrors.ErrInputParameter, "Destination not match: get %s want %s", authReq.Destination, idp.getRedirectLoginUrl(idpId))
}
if authReq.AssertionConsumerServiceURL != sp.GetPostAssertionConsumerServiceUrl() {
return "", errors.Wrapf(httperrors.ErrInputParameter, "AssertionConsumerServiceURL not match: get %s want %s", authReq.AssertionConsumerServiceURL, sp.GetPostAssertionConsumerServiceUrl())
}
- resp, err := idp.getLoginResponse(ctx, authReq, sp)
+ resp, err := idp.getLoginResponse(ctx, authReq, idpId, sp)
if err != nil {
return "", errors.Wrap(err, "getLoginResponse")
}
@@ -255,8 +273,11 @@ func (idp *SSAMLIdpInstance) getServiceProvider(eId string) *SSAMLServiceProvide
return nil
}
-func (idp *SSAMLIdpInstance) getLoginResponse(ctx context.Context, req samlutils.AuthnRequest, sp *SSAMLServiceProvider) (*samlutils.Response, error) {
- data := idp.onSpInitiatedLogin(ctx, sp)
+func (idp *SSAMLIdpInstance) getLoginResponse(ctx context.Context, req samlutils.AuthnRequest, idpId string, sp *SSAMLServiceProvider) (*samlutils.Response, error) {
+ data, err := idp.onSpInitiatedLogin(ctx, idpId, sp)
+ if err != nil {
+ return nil, errors.Wrap(err, "idp.onSpInitiatedLogin")
+ }
input := samlutils.SSAMLResponseInput{
IssuerCertString: idp.saml.GetCertString(),
IssuerEntityId: idp.saml.GetEntityId(),
@@ -274,15 +295,10 @@ func (idp *SSAMLIdpInstance) processIdpInitiatedLogin(ctx context.Context, input
if sp == nil {
return "", errors.Wrapf(httperrors.ErrResourceNotFound, "issuer %s not found", input.EntityID)
}
- var state []byte
- if len(input.State) > 0 {
- var err error
- state, err = samlutils.SAMLDecode(input.State)
- if err != nil {
- return "", errors.Wrapf(httperrors.ErrInputParameter, "invalid state %s: %s", input.State, err)
- }
+ data, err := idp.onIdpInitiatedLogin(ctx, sp, input.IdpId)
+ if err != nil {
+ return "", errors.Wrap(err, "idp.onIdpInitiatedLogin")
}
- data := idp.onIdpInitiatedLogin(ctx, sp, string(state))
respInput := samlutils.SSAMLResponseInput{
IssuerCertString: idp.saml.GetCertString(),
IssuerEntityId: idp.saml.GetEntityId(),
diff --git a/pkg/util/samlutils/types.go b/pkg/util/samlutils/types.go
index cd3aff3832..96a45ff338 100644
--- a/pkg/util/samlutils/types.go
+++ b/pkg/util/samlutils/types.go
@@ -249,7 +249,7 @@ type SIdpRedirectLoginInput struct {
type SIdpInitiatedLoginInput struct {
EntityID string `json:"EntityID"`
- State string `json:"State"`
+ IdpId string `json:"IdpId"`
}
type Issuer struct {