diff --git a/bindep.txt b/bindep.txt index 44eb696bc2..6d3d592fb5 100644 --- a/bindep.txt +++ b/bindep.txt @@ -34,3 +34,6 @@ python3-devel [platform:rpm] libmariadb-devel [platform:suse] openldap2-devel [platform:suse] + +# Required for sphinx graphviz image generation +graphviz [test doc] diff --git a/doc/requirements.txt b/doc/requirements.txt index b59c46936d..b7023fe12e 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -4,9 +4,7 @@ openstackdocstheme>=2.2.1 # Apache-2.0 sphinx>=2.0.0,!=2.1.0 # BSD sphinxcontrib-apidoc>=0.2.0 # BSD -sphinxcontrib-seqdiag>=0.8.4 # BSD sphinx-feature-classification>=0.3.2 # Apache-2.0 -sphinxcontrib-blockdiag>=1.5.5 # BSD reno>=3.1.0 # Apache-2.0 os-api-ref>=1.4.0 # Apache-2.0 python-ldap>=3.0.0 # PSF diff --git a/doc/source/admin/federation/introduction.rst b/doc/source/admin/federation/introduction.rst index 36fca14937..7ebd693612 100644 --- a/doc/source/admin/federation/introduction.rst +++ b/doc/source/admin/federation/introduction.rst @@ -117,20 +117,51 @@ process is key to being able to debug later on. Normal keystone --------------- -.. seqdiag:: +.. graphviz:: :name: normal-keystone :alt: Diagram of keystone's normal auth flow, in which a user agent authenticates and authorizes themself with keystone and obtains a scoped token to pass to an OpenStack service. - seqdiag { - default_fontsize = 13; - useragent [label = "User Agent"]; keystone [label = "Keystone"]; openstack [label = "OpenStack"]; - useragent -> keystone [label = "GET /v3/auth/tokens"]; - keystone -> keystone [label = "Authenticate"]; - keystone -> keystone [label = "Authorize"]; - useragent <- keystone [label = "Scoped token"]; - useragent -> openstack [label = "GET /v2.1/servers"]; + digraph { + nodesep=1 + node [shape=point] + + // first column + useragent_top [label = "User Agent" shape="box"] + useragent_bottom [label = "User Agent" shape="box"] + // chain rows of the column + useragent_top -> u0 [arrowhead="none" style="dashed"] + u0 -> u1 -> u2 [arrowhead="none" style="bold"] + u2 -> useragent_bottom [arrowhead="none" style="dashed"] + + // second column + keystone_top [label = "Keystone" shape="box"] + keystone_bottom [label = "Keystone" shape="box"] + keystone_top -> k0 [arrowhead="none" style="dashed"] + k0 -> k1 -> k2 -> k3 [arrowhead="none" style="bold"] + k3 -> keystone_bottom [arrowhead="none" style="dashed"] + + openstack_top [label = "OpenStack" shape="box"] + openstack_bottom [label = "OpenStack" shape="box"] + openstack_top -> o0 -> openstack_bottom [arrowhead="none" style="dashed"] + + // Order columns + useragent_top -> keystone_top -> openstack_top [style="invis"] + + // Event links + u0 -> k0 [weight=0 label = "GET /v3/auth/tokens"] + k1 -> k1 [weight=0 label = "Authenticate"] + k2 -> k2 [weight=0 label = "Authorize"] + u1 -> k3 [weight=0 label = "Scoped token" dir="back"] + u2 -> o0 [weight=0 xlabel = "GET /v2.1/servers"] + + // bind nodes to levels + {rank=same; useragent_top keystone_top openstack_top } + {rank=same; u0 k0 } + {rank=same; u1 k3 } + {rank=same; u2 o0 } + {rank=same; useragent_bottom keystone_bottom openstack_bottom } } In a normal keystone flow, the user requests a scoped token directly from @@ -147,26 +178,61 @@ SAML2.0 SAML2.0 WebSSO ~~~~~~~~~~~~~~ -.. seqdiag:: +.. graphviz:: :name: saml2-websso :alt: Diagram of a standard WebSSO authentication flow. - seqdiag { - edge_length = 325; - default_fontsize = 13; - useragent [label = "User Agent"]; sp [label = "Service Provider"]; idp [label = "Identity Provider"]; - useragent -> sp [label = "GET /secure"]; - useragent <- sp [label = "HTTP 302 - Location: https://idp/auth? - SAMLRequest=req"]; - useragent -> idp [label = "GET /auth?SAMLRequest=req"]; - idp -> idp [label = "Authenticate"]; - useragent <- idp [label = "HTTP 200 - SAMLResponse in HTML form"]; - useragent -> sp [label = "POST /assertionconsumerservice"]; - sp -> sp [label = "Validate"]; - useragent <- sp [label = "HTTP 302; Location: /secure"]; - useragent -> sp [label = "GET /secure"]; + digraph { + nodesep=1 + node [shape=point] + + // first column + useragent_top [label = "User Agent" shape="box"] + useragent_bottom [label = "User Agent" shape="box"] + // chain rows of the column + useragent_top -> u0 [arrowhead="none" style="dashed"] + u0 -> u1 -> u2 -> u3 -> u4 -> u5 -> u6 [arrowhead="none" style="bold"] + u6 -> useragent_bottom [arrowhead="none" style="dashed"] + + // second column + sp_top [label = "Service Provider" shape="box"] + sp_bottom [label = "Service Provider" shape="box"] + sp_top -> sp0 [arrowhead="none" style="dashed"] + sp0 -> sp1 [arrowhead="none" style="bold"] + sp1 -> sp2 [arrowhead="none" style="dashed"] + sp2 -> sp3 -> sp4 [arrowhead="none" style="bold"] + sp4 -> sp5 -> sp_bottom [arrowhead="none" style="dashed"] + + idp_top [label = "Identity Provider" shape="box"] + idp_bottom [label = "Identity Provider" shape="box"] + idp_top -> idp0 [arrowhead="none" style="dashed"] + idp0 -> idp1 -> idp2 [arrowhead="none" style="bold"] + idp2 -> idp_bottom [arrowhead="none" style="dashed"] + + // Order columns + useragent_top -> sp_top -> idp_top [style="invis"] + + // Event links + u0 -> sp0 [weight=0 xlabel = "GET /secure"] + u1 -> sp1 [weight=0 xlabel = "HTTP 302\nLocation: https://idp/auth?SAMLRequest=req" dir=back] + u2 -> idp0 [weight=0 xlabel = "GET /auth?SAMLRequest=req"] + idp1 -> idp1 [weight=0 xlabel = "Authenticate"] + u3 -> idp2 [weight=0 xlabel = "HTTP 200\nSAMLResponse in HTML form" dir=back] + u4 -> sp2 [weigth=0 xlabel= "POST /assertionconsumerservice"] + sp3 -> sp3 [weight=0 xlabel = "Validate"] + u5 -> sp4 [ weight=0 xlabel = "HTTP 302; Location: /secure" dir=back] + u6 -> sp5 [weight=0 xlabel = "GET /secure"] + + // bind nodes to levels + {rank=same; useragent_top sp_top idp_top } + {rank=same; u0 sp0 } + {rank=same; u1 sp1 } + {rank=same; u2 idp0 } + {rank=same; u3 idp2 } + {rank=same; u4 sp2 } + {rank=same; u5 sp4 } + {rank=same; u6 sp5 } + {rank=same; useragent_bottom sp_bottom idp_bottom } } This diagram shows a standard `WebSSO`_ authentication flow, not one involving @@ -194,25 +260,60 @@ redirect back to the original resource the user had requested. SAML2.0 ECP ~~~~~~~~~~~ -.. seqdiag:: +.. graphviz:: :name: saml2-ecp :alt: Diagram of a standard ECP authentication flow. - seqdiag { - default_fontsize = 13; - useragent [label = "User Agent"]; sp [label = "Service Provider"]; idp [label = "Identity Provider"]; - useragent -> sp [label = "GET /secure"]; - useragent <- sp [label = "HTTP 200 - SAML Request"]; - useragent -> idp [label = "POST /auth - SAML Request"]; - idp -> idp [label = "Authenticate"]; - useragent <- idp [label = "HTTP 200 - SAMLResponse in SOAP"]; - useragent -> sp [label = "POST /responseconsumer"]; - sp -> sp [label = "Validate"]; - useragent <- sp [label = "HTTP 200 /secure"]; - } + digraph { + nodesep=1 + node [shape=point] + + // first column + useragent_top [label = "User Agent" shape="box"] + useragent_bottom [label = "User Agent" shape="box"] + // chain rows of the column + useragent_top -> u0 [arrowhead="none" style="dashed"] + u0 -> u1 -> u2 -> u3 -> u4 -> u5 [arrowhead="none" style="bold"] + u5 -> useragent_bottom [arrowhead="none" style="dashed"] + + // second column + sp_top [label = "Service Provider" shape="box"] + sp_bottom [label = "Service Provider" shape="box"] + sp_top -> sp0 [arrowhead="none" style="dashed"] + sp0 -> sp1 [arrowhead="none" style="bold"] + sp1 -> sp2 [arrowhead="none" style="dashed"] + sp2 -> sp3 -> sp4 [arrowhead="none" style="bold"] + sp4 -> sp_bottom [arrowhead="none" style="dashed"] + + idp_top [label = "Identity Provider" shape="box"] + idp_bottom [label = "Identity Provider" shape="box"] + idp_top -> idp0 [arrowhead="none" style="dashed"] + idp0 -> idp1 -> idp2 [arrowhead="none" style="bold"] + idp2 -> idp_bottom [arrowhead="none" style="dashed"] + + // Order columns + useragent_top -> sp_top -> idp_top [style="invis"] + + // Event links + u0 -> sp0 [weight=0 xlabel = "GET /secure"] + u1 -> sp1 [weight=0 xlabel = "HTTP 200\nSAML Request" dir=back] + u2 -> idp0 [weight=0 xlabel = "POST /auth\nSAML Request"] + idp1 -> idp1 [weight=0 xlabel = "Authenticate"] + u3 -> idp2 [weight=0 xlabel = "HTTP 200\nSAMLResponse in SOAP" dir=back] + u4 -> sp2 [weigth=0 xlabel= "POST /assertionconsumerservice"] + sp3 -> sp3 [weight=0 xlabel = "Validate"] + u5 -> sp4 [ weight=0 xlabel = "HTTP 200; Location: /secure" dir=back] + + // bind nodes to levels + {rank=same; useragent_top sp_top idp_top } + {rank=same; u0 sp0 } + {rank=same; u1 sp1 } + {rank=same; u2 idp0 } + {rank=same; u3 idp2 } + {rank=same; u4 sp2 } + {rank=same; u5 sp4 } + {rank=same; useragent_bottom sp_bottom idp_bottom } + } `ECP`_ is another SAML profile. Generally the flow is similar to the WebSSO flow, but it is designed for a client that natively understands SAML, for @@ -231,36 +332,89 @@ WebSSO with keystone and horizon ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. seqdiag:: +.. graphviz:: :name: saml2-keystone-horizon :alt: Diagram of the SAML2.0 WebSSO auth flow specific to horizon, keystone, and the HTTPD module acting as service provider. - seqdiag { - default_fontsize = 13; - useragent [label = "User Agent"]; horizon [label = "Horizon"]; httpd [label = "HTTPD", color = "lightgrey"]; keystone [label = "Keystone", color = "lightgrey"]; idp [label = "Identity Provider"]; - useragent -> horizon [label = "POST /auth/login"]; - useragent <- horizon [label = "HTTP 302 - Location: - /v3/auth/OS-FEDERATION - /websso/saml2"]; - useragent -> httpd [label = "GET /v3/auth/OS-FEDERATION/websso/saml2"]; - useragent <- httpd [label = "HTTP 302 - Location: https://idp/auth?SAMLRequest=req"]; - useragent -> idp [label = "GET /auth"]; - idp -> idp [label = "Authenticate"]; - useragent <- idp [label = "HTTP 200 - SAMLResponse in HTML form"]; - useragent -> httpd [label = "POST /assertionconsumerservice"]; - httpd -> httpd [label = "Validate"]; - useragent <- httpd [label = "HTTP 302 - Location: /v3/auth/OS-FEDERATION/websso/saml2"]; - useragent -> keystone [label = "GET /v3/auth/OS-FEDERATION/websso/saml2"]; - keystone -> keystone [label = "Issue token"]; - useragent <- keystone [label = "HTTP 200 - HTML form containing unscoped token"]; - useragent -> horizon [label = "POST /auth/websso"]; - useragent <- horizon [label = "successful login"]; + digraph { + nodesep=1 + node [shape=point] + + // first column + useragent_top [label = "User Agent" shape="box"] + useragent_bottom [label = "User Agent" shape="box"] + // chain rows of the column + useragent_top -> u0 [arrowhead="none" style="dashed"] + u0 -> u1 -> u2 -> u3 -> u4 -> u5 -> u6 -> u7 -> u8 -> u9 -> u10 -> u11 [arrowhead="none" style="bold"] + u11 -> useragent_bottom [arrowhead="none" style="bold"] + + // second column + h_top [label = "Horizon" shape="box"] + h_bottom [label = "Horizon" shape="box"] + h_top -> h0 [arrowhead="none" style="dashed"] + h0 -> h1 [arrowhead="none" style="bold"] + h1 -> h2 [arrowhead="none" style="dashed"] + h2 -> h3 [arrowhead="none" style="bold"] + h3 -> h_bottom [arrowhead="none" style="dashed"] + + // second column + http_top [label = "Httpd" shape="box"] + http_bottom [label = "Httpd" shape="box"] + http_top -> http0 [arrowhead="none" style="dashed"] + http0 -> http1 [arrowhead="none" style="bold"] + http1 -> http2 [arrowhead="none" style="dashed"] + http2 -> http3 -> http4 [arrowhead="none" style="bold"] + http4 -> http_bottom [arrowhead="none" style="dashed"] + + // second column + k_top [label = "Keystone" shape="box"] + k_bottom [label = "Keystone" shape="box"] + k_top -> k0 [arrowhead="none" style="dashed"] + k0 -> k1 -> k2 [arrowhead="none" style="bold"] + k2 -> k_bottom [arrowhead="none" style="dashed"] + + idp_top [label = "Identity Provider" shape="box"] + idp_bottom [label = "Identity Provider" shape="box"] + idp_top -> idp0 [arrowhead="none" style="dashed"] + idp0 -> idp1 -> idp2 [arrowhead="none" style="bold"] + idp2 -> idp_bottom [arrowhead="none" style="dashed"] + + // Order columns + useragent_top -> h_top -> http_top -> k_top -> idp_top [style="invis"] + + // Event links + u0 -> h0 [weight=0 xlabel = "POST /v3/auth/tokens"] + u1 -> h1 [weight=0 xlabel = "HTTP 302\nLocation: /v3/auth/OS-FEDERATION/webssol/saml2" dir=back] + u2 -> http0 [weight=0 xlabel = "GET /v3/auth/OS-FEDERATION/websso/saml2"] + u3 -> http1 [weight=0 xlabel = "HTTP 302\nLocation: https://idp/auth?SAMLRequest=req" dir=back] + u4 -> idp0 [weight=0 xlabel = "GET /auth"] + idp1 -> idp1 [weight=0 xlabel = "Authenticate"] + u5 -> idp2 [weight=0 xlabel = "HTTP 200\nSAMLResonse in HTML form" dir=back] + u6 -> http2 [weight=0 xlabel = "POST /assertionconsumerservice"] + http3 -> http3 [weight=0 xlabel = "Validate"] + u7 -> http4 [weight=0 xlabel = "HTTP 302\nLocation: /v3/auth/OS-FEDERATION/websso/saml2" dir=back] + u8 -> k0 [weight=0 xlabel="GET /v3/auth/OS-FEDERATION/websso/saml2"] + k1 -> k1 [weight=0 xlabel="Issue token"] + u9 -> k2 [weight=0 xlabel="HTTP 200\nHTML form containing unscoped token" dir=back] + u10 -> h2 [weight=0 xlabel="POST /auth/websso"] + u11 -> h3 [weight=0 xlabel="successful login" dir=back] + + // bind nodes to levels + {rank=same;useragent_top;h_top;http_top;k_top;idp_top} + {rank=same;u0;h0 } + {rank=same;u1;h1 } + {rank=same;u2;http0 } + {rank=same;u3;http1 } + {rank=same;u4;idp0 } + {rank=same;u5;idp2 } + {rank=same;u6;http2 } + {rank=same;u7;http4 } + {rank=same;u8;k0 } + {rank=same;u9;k2 } + {rank=same;u10;h2} + {rank=same;u11;h3} + {rank=same;useragent_bottom;h_bottom;http_bottom;k_bottom;idp_bottom} } Keystone is not a web front-end, which means horizon needs to handle some parts @@ -292,29 +446,68 @@ secure resource it requests from keystone. Keystone to Keystone ~~~~~~~~~~~~~~~~~~~~ -.. seqdiag:: +.. graphviz:: :name: keystone-to-keystone :alt: Diagram of the IdP-initiated auth flow in a keystone-to-keystone model. - seqdiag { - edge_length = 240; - default_fontsize = 13; - useragent [label = "User Agent"]; sp [label = "Service Provider"]; idp [label = "Identity Provider"]; - useragent -> idp [label = "POST /v3/auth/tokens"]; - idp -> idp [label = "Authenticate"]; - useragent <- idp [label = "HTTP 201 - X-Subject-Token: token"]; - useragent -> idp [label = "POST /v3/auth/OS-FEDERATION/saml2/ecp"]; - useragent <- idp [label = "HTTP 201 - SAMLResponse in SOAP envelope"]; - useragent -> sp [label = "POST /PAOS-url"]; - sp -> sp [label = "Validate"]; - useragent <- sp [label = "HTTP 302"]; - useragent -> sp [label = "GET /v3/OS-FED/.../auth"]; - useragent <- sp [label = "HTTP 201 - X-Subject-Token: unscoped token"]; - useragent -> sp [label = "POST /v3/auth/tokens - (request scoped token)"]; + digraph { + nodesep=1 + node [shape=point] + + // first column + useragent_top [label = "User Agent" shape="box"] + useragent_bottom [label = "User Agent" shape="box"] + // chain rows of the column + useragent_top -> u0 [arrowhead="none" style="dashed"] + u0 -> u1 -> u2 -> u3 -> u4 -> u5 -> u6 -> u7 -> u8 [arrowhead="none" style="bold"] + u8 -> useragent_bottom [arrowhead="none" style="dashed"] + + // second column + sp_top [label = "Service Provider" shape="box"] + sp_bottom [label = "Service Provider" shape="box"] + sp_top -> sp0 [arrowhead="none" style="dashed"] + sp0 -> sp1 -> sp2 [arrowhead="none" style="bold"] + sp2 -> sp3 [arrowhead="none" style="dashed"] + sp3 -> sp4 [arrowhead="none" style="bold"] + sp4 -> sp5 [arrowhead="none" style="dashed"] + sp5 -> sp_bottom [arrowhead="none" style="dashed"] + + idp_top [label = "Identity Provider" shape="box"] + idp_bottom [label = "Identity Provider" shape="box"] + idp_top -> idp0 [arrowhead="none" style="dashed"] + idp0 -> idp1 -> idp2 [arrowhead="none" style="bold"] + idp2 -> idp3 [arrowhead="none" style="dashed"] + idp3 -> idp4 [arrowhead="none" style="bold"] + idp4 -> idp_bottom [arrowhead="none" style="dashed"] + + // Order columns + useragent_top -> sp_top -> idp_top [style="invis"] + + // Event links + u0 -> idp0 [weight=0 xlabel = "POST /v3/auth/tokens"] + idp1 -> idp1 [weight=0 xlabel = "Authenticate"] + u1 -> idp2 [weight=0 xlabel = "HTTP 201\nX-Subject-Token: token" dir=back] + u2 -> idp3 [weight=0 xlabel = "POST /v3/auth/OS-FEDERATION/saml2/ecp"] + u3 -> idp4 [weight=0 xlabel = "HTTP 201\nSAMLResponse in SOAP envelope" dir=back] + u4 -> sp0 [weight=0 xlabel = "POST /PAOS-url"] + sp1 -> sp1 [weight=0 xlabel = "Validate"] + u5 -> sp2 [weight=0 xlabel = "HTTP 302" dir=back] + u6 -> sp3 [weight=0 xlabel = "GET /v3/OS-FED/.../auth"] + u7 -> sp4 [weight=0 xlabel = "HTTP 201\nX-Subject-Token: unscoped token" dir=back] + u8 -> sp5 [weight=0 xlabel = "POST /v3/auth/tokens\n(request scoped token)"] + + // bind nodes to levels + {rank=same;useragent_top;sp_top;idp_top} + {rank=same;u0;idp0 } + {rank=same;u1;idp2 } + {rank=same;u2;idp3 } + {rank=same;u3;idp4 } + {rank=same;u4;sp0 } + {rank=same;u5;sp2 } + {rank=same;u6;sp3 } + {rank=same;u7;sp4 } + {rank=same;u8;sp5 } + {rank=same;useragent_bottom;sp_bottom;idp_bottom} } When keystone is used as an Identity Provider in a Keystone to Keystone @@ -339,31 +532,67 @@ OpenID Connect OpenID Connect Authentication Flow ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. seqdiag:: +.. graphviz:: :name: openidc :alt: Diagram of a standard OpenID Connect authentication flow :align: left - seqdiag { - edge_length = 330; - default_fontsize = 13; - useragent [label = "User Agent"]; sp [label = "Service Provider"]; idp [label = "Identity Provider"]; - useragent -> sp [label = "GET /secure"]; - useragent <- sp [label = "HTTP 302 - Location: https://idp/auth? - client_id=XXX&redirect_uri=https://sp/secure"]; - useragent -> idp [label = "GET /auth?client_id=XXX&redirect_uri=https://sp/secure"]; - idp -> idp [label = "Authenticate"]; - useragent <- idp [label = "HTTP 302 - Location: https://sp/auth?code=XXX"]; - useragent -> sp [label = "GET /auth?code=XXX"]; - sp -> idp [label = "POST https://idp/token - code=XXX&redirect_uri=https://sp/secure"]; - sp <- idp [label = "HTTP 200 - {\"access_code\": \"XXX\", - \"id_token\": \"XXX\"}"]; - useragent <- sp [label = "HTTP 302; Location: /secure"]; - useragent -> sp [label = "GET /secure"]; + digraph auth { + nodesep=1 + node [shape=point] + + // first column + useragent_top [label = "User Agent" shape="box"] + useragent_bottom [label = "User Agent" shape="box"] + // chain rows of the column + useragent_top -> u0 [arrowhead="none" style="dashed"] + u0 -> u1 -> u2 -> u3 -> u4 -> u5 -> u6 [arrowhead="none" style="bold"] + u6 -> useragent_bottom [arrowhead="none" style="dashed"] + + // second column + sp_top [label = "Service Provider" shape="box"] + sp_bottom [label = "Service Provider" shape="box"] + sp_top -> sp0 [arrowhead="none" style="dashed"] + sp0 -> sp1 [arrowhead="none" style="bold"] + sp1 -> sp2 [arrowhead="none" style="dashed"] + sp2 -> sp3 -> sp4 -> sp5 [arrowhead="none" style="bold"] + sp5 -> sp6 -> sp_bottom [arrowhead="none" style="dashed"] + + idp_top [label = "Identity Provider" shape="box"] + idp_bottom [label = "Identity Provider" shape="box"] + idp_top -> idp0 [arrowhead="none" style="dashed"] + idp0 -> idp1 -> idp2 [arrowhead="none" style="bold"] + idp2 -> idp3 [arrowhead="none" style="dashed"] + idp3 -> idp4 [arrowhead="none" style="bold"] + idp4 -> idp_bottom [arrowhead="none" style="dashed"] + + // Order columns + useragent_top -> sp_top -> idp_top [style="invis"] + + // Event links + u0 -> sp0 [weight=0 xlabel = "GET /secure"] + u1 -> sp1 [weight=0 xlabel = "HTTP 302\nLocation: https://idp/auth?client_id=XXX&redirect_uri=https://sp/secure" dir=back] + u2 -> idp0 [weight=0 xlabel = "GET /auth?client_id=XXX&redirect_uri=https://sp/secure"] + idp1 -> idp1 [weight=0 xlabel = "Authenticate"] + u3 -> idp2 [weight=0 xlabel = "HTTP 302\nLocation: https://sp/auth?code=XXX" dir=back] + u4 -> sp2 [weight=0 xlabel = "GET /auth?code=XXX"] + sp3 -> idp3 [weight=0 xlabel = "POST https://idp/token?code=XXX&redirect_uri=https://sp/secure"] + sp4 -> idp4 [weight=0 xlabel = "HTTP 200\n{\"access_code\": \"XXX\", \"id_token\": \"XXX\"}" dir=back] + u5 -> sp5 [weight=0 xlabel = "HTTP 302; Location: /secure" dir=back] + u6 -> sp6 [weight=0 xlabel = "GET /secure"] + + // bind nodes to levels + {rank=same;useragent_top;sp_top;idp_top} + {rank=same;u0;sp0} + {rank=same;u1;sp1} + {rank=same;u2;idp0} + {rank=same;u3;idp2} + {rank=same;u4;sp2} + {rank=same;sp3;idp3} + {rank=same;sp4;idp4} + {rank=same;u5;sp5} + {rank=same;u6;sp6} + {rank=same;useragent_bottom;sp_bottom;idp_bottom } } OpenID Connect is different from any SAML2.0 flow because the negotiation is not @@ -382,47 +611,93 @@ and exchange it for an ID token. OpenID Connect with keystone and horizon ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. seqdiag:: +.. graphviz:: :name: oidc-keystone-horizon :alt: Diagram of the OpenID Connect WebSSO auth flow specific to horizon, keystone, and the HTTPD module acting as service provider. - seqdiag { - edge_length = 200 - default_fontsize = 13; - useragent [label = "User Agent"]; horizon [label = "Horizon"]; httpd [label = "HTTPD", color = "lightgrey"]; keystone [label = "Keystone", color = "lightgrey"]; idp [label = "Identity Provider"]; - useragent -> horizon [label = "POST /auth/login"]; - useragent <- horizon [label = "HTTP 302 - Location: - /v3/auth/OS-FEDERATION - /websso/openid"]; - useragent -> httpd [label = "GET /v3/auth/OS-FEDERATION/websso/openid"]; - useragent <- httpd [label = "HTTP 302 - Location: - https://idp/auth? - client_id=XXX& - redirect_uri=https://sp/v3/auth/OS-FEDERATION/websso"]; - useragent -> idp [label = "GET /auth?client_id=XXX& - redirect_uri=https://sp/v3/auth/OS-FEDERATION/websso"]; - idp -> idp [label = "Authenticate"]; - useragent <- idp [label = "HTTP 302 - Location: https://sp/v3/auth/OS-FEDERATION/websso"]; - useragent -> httpd [label = "GET /v3/auth/OS-FEDERATION/websso"]; - httpd -> idp [label = "POST https://idp/token - code=XXX& - redirect_uri=https://sp/v3/auth/OS-FEDERATION/websso"]; - httpd <- idp [label = "HTTP 200 - {\"access_code\": \"XXX\", - \"id_token\": \"XXX\"}"]; - useragent <- httpd [label = "HTTP 302 - Location: /v3/auth/OS-FEDERATION/websso/mapped"]; - useragent -> keystone [label = "GET /v3/auth/OS-FEDERATION/websso/mapped"]; - keystone -> keystone [label = "Issue token"]; - useragent <- keystone [label = "HTTP 200 - HTML form containing unscoped token"]; - useragent -> horizon [label = "POST /auth/websso"]; - useragent <- horizon [label = "successful login"]; - } + digraph { + nodesep=1 + node [shape=point] + + // first column + useragent_top [label = "User Agent" shape="box"] + useragent_bottom [label = "User Agent" shape="box"] + // chain rows of the column + useragent_top -> u0 [arrowhead="none" style="dashed"] + u0 -> u1 -> u2 -> u3 -> u4 -> u5 -> u6 -> u7 -> u8 -> u9 -> u10 -> u11 [arrowhead="none" style="bold"] + u11 -> useragent_bottom [arrowhead="none" style="dashed"] + + // second column + h_top [label = "Horizon" shape="box"] + h_bottom [label = "Horizon" shape="box"] + h_top -> h0 [arrowhead="none" style="dashed"] + h0 -> h1 [arrowhead="none" style="bold"] + h1 -> h2 [arrowhead="none" style="dashed"] + h2 -> h3 [arrowhead="none" style="bold"] + h3 -> h_bottom [arrowhead="none" style="dashed"] + + http_top [label = "Httpd" shape="box"] + http_bottom [label = "Httpd" shape="box"] + http_top -> http0 [arrowhead="none" style="dashed"] + http0 -> http1 [arrowhead="none" style="bold"] + http1 -> http2 [arrowhead="none" style="dashed"] + http2 -> http3 -> http4 -> http5 [arrowhead="none" style="bold"] + http5 -> http_bottom [arrowhead="none" style="dashed"] + + k_top [label = "Keystone" shape="box"] + k_bottom [label = "Keystone" shape="box"] + k_top -> k0 [arrowhead="none" style="dashed"] + k0 -> k1 -> k2 [arrowhead="none" style="bold"] + k2 -> k_bottom [arrowhead="none" style="dashed"] + + idp_top [label = "Identity Provider" shape="box"] + idp_bottom [label = "Identity Provider" shape="box"] + idp_top -> idp0 [arrowhead="none" style="dashed"] + idp0 -> idp1 -> idp2 [arrowhead="none" style="bold"] + idp2 -> idp3 [arrowhead="none" style="dashed"] + idp3 -> idp4 [arrowhead="none" style="bold"] + idp4 -> idp_bottom [arrowhead="none" style="dashed"] + + // Order columns + useragent_top -> h_top -> http_top -> k_top -> idp_top [style="invis"] + + // Event links + u0 -> h0 [weight=0 xlabel = "POST /v3/auth/tokens"] + u1 -> h1 [weight=0 xlabel = "HTTP 302\nLocation: /v3/auth/OS-FEDERATION/webssol/openid" dir=back] + u2 -> http0 [weight=0 xlabel = "GET /v3/auth/OS-FEDERATION/websso/openid"] + u3 -> http1 [weight=0 xlabel = "HTTP 302\nLocation: https://idp/auth?client_id=XXX&redirect_uri=https://sp/v3/auth/OS-FEDERATION/websso" dir=back] + u4 -> idp0 [weight=0 xlabel = "GET /auth?client_id=XXX&redirect_uri=https://sp/v3/auth/OS-FEDERATION/websso"] + idp1 -> idp1 [weight=0 xlabel = "Authenticate"] + u5 -> idp2 [weight=0 xlabel = "HTTP 203\nLocation: https://sp/v3/auth/OS-FEDERATION/websso" dir=back] + u6 -> http2 [weight=0 xlabel = "GET /v3/auth/OS-FEDERATION/websso"] + http3 -> idp3 [weight=0 xlabel = "POST https://idp/tokencode=XXX&redirect_uri=https://sp/v3/auth/OS-FEDERATION/websso"] + http4 -> idp4 [weight=0 xlabel = "HTTP 200\n{\"access_code\": \"XXX\"\n\"id_token\": \"XXX\"}" dir=back] + u7 -> http5 [weight=0 xlabel = "HTTP 302\nLocation: /v3/auth/OS-FEDERATION/websso/mapped" dir=back] + u8 -> k0 [weight=0 xlabel="GET /v3/auth/OS-FEDERATION/websso/mapped"] + k1 -> k1 [weight=0 xlabel="Issue token"] + u9 -> k2 [weight=0 xlabel="HTTP 200\nHTML form containing unscoped token" dir=back] + u10 -> h2 [weight=0 xlabel="POST /auth/websso"] + u11 -> h3 [weight=0 xlabel="successful login" dir=back] + + // bind nodes to levels + {rank=same;useragent_top;h_top;http_top;k_top;idp_top} + {rank=same;u0;h0 } + {rank=same;u1;h1 } + {rank=same;u2;http0 } + {rank=same;u3;http1 } + {rank=same;u4;idp0 } + {rank=same;u5;idp2 } + {rank=same;u6;http2 } + {rank=same;http3;idp3 } + {rank=same;http4;idp4 } + {rank=same;u7;http5 } + {rank=same;u8;k0 } + {rank=same;u9;k2 } + {rank=same;u10;h2} + {rank=same;u11;h3} + {rank=same;useragent_bottom;h_bottom;http_bottom;k_bottom;idp_bottom} + } From horizon and keystone's point of view, the authentication flow is the same for OpenID Connect as it is for SAML2.0. It is only the HTTPD OpenIDC module diff --git a/doc/source/admin/unified-limits.rst b/doc/source/admin/unified-limits.rst index 1d4498647b..e994cfd077 100644 --- a/doc/source/admin/unified-limits.rst +++ b/doc/source/admin/unified-limits.rst @@ -174,10 +174,11 @@ child of project `Alpha`. All projects assume a default limit of 10 cores via a registered limit. The labels in the diagrams below use shorthand notation for `limit` and `usage` as `l` and `u`, respectively: -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha [label="Alpha (u=0)"]; Beta [label=" Beta (u=0)"]; @@ -188,10 +189,11 @@ Each project may use up to 10 cores because of the registered limit and none of the projects have an override. Using flat enforcement, you're allowed to ``UPDATE LIMIT on Alpha to 20``: -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha [label="Alpha (l=20, u=0)", textcolor = "#00af00"]; Beta [label=" Beta (u=0)"]; @@ -202,10 +204,11 @@ the projects have an override. Using flat enforcement, you're allowed to You're also allowed to ``UPDATE LIMIT on Charlie to 30``, even though `Charlie` is a sub-project of both `Beta` and `Alpha`. -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha [label="Alpha (l=20, u=0)"]; Beta [label=" Beta (u=0)"]; @@ -220,10 +223,11 @@ Conversely, you can simulate hierarchical enforcement by adjusting limits through the project tree manually. For example, let's still assume 10 is the default limit imposed by an existing registered limit: -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha [label="Alpha (u=0)"]; Beta [label=" Beta (u=0)"]; @@ -232,10 +236,11 @@ default limit imposed by an existing registered limit: You may set a project-specific override to ``UPDATE LIMIT on Alpha to 30``: -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha [label="Alpha (l=30, u=0)", textcolor = "#00af00"]; Beta [label=" Beta (u=0)"]; @@ -244,10 +249,11 @@ You may set a project-specific override to ``UPDATE LIMIT on Alpha to 30``: Next you can ``UPDATE LIMIT on Beta to 20``: -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha [label="Alpha (l=30, u=0)"]; Beta [label=" Beta (l=20, u=0)", textcolor = "#00af00"]; @@ -258,10 +264,11 @@ Theoretically, the entire project tree consisting of `Alpha`, `Beta`, and `Charlie` is limited to 60 cores. If you'd like to ensure only 30 cores are used within the entire hierarchy, you can ``UPDATE LIMIT on Alpha to 0``: -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha [label="Alpha (l=0, u=0)", textcolor = "#00af00"]; Beta [label=" Beta (l=20, u=0)"]; @@ -322,10 +329,11 @@ is cores and the default registered limit for cores is 10. Also assume we have the following project hierarchy where `Alpha` has a limit of 20 cores and its usage is currently 4: -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha -> Beta; Alpha -> Charlie; @@ -337,10 +345,11 @@ usage is currently 4: Technically, both `Beta` and `Charlie` can use up to 8 cores each: -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha -> Beta; Alpha -> Charlie; @@ -356,10 +365,11 @@ and check the usage of each project in the hierarchy to see that the total usage of `Alpha`, `Beta`, and `Charlie` is equal to the limit of the tree, set by `Alpha.limit`: -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha -> Beta; Alpha -> Charlie; @@ -372,10 +382,11 @@ by `Alpha.limit`: Despite the usage of the tree being equal to the limit, we can still add children to the tree: -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha -> Beta; Alpha -> Charlie; @@ -390,10 +401,11 @@ children to the tree: Even though the project can be created, the current usage of cores across the tree prevents `Delta` from claiming any cores: -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha -> Beta; Alpha -> Charlie; @@ -408,10 +420,11 @@ tree prevents `Delta` from claiming any cores: Creating a grandchild of project `Alpha` is forbidden because it violates the two-level hierarchical constraint: -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha -> Beta; Alpha -> Charlie; @@ -434,10 +447,11 @@ but may be implemented with a separate model. Granting `Beta` the ability to claim more cores can be done by giving `Beta` a project-specific override for cores -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha -> Beta; Alpha -> Charlie; @@ -451,10 +465,11 @@ Note that regardless of this update, any subsequent requests to claim more cores in the tree will be rejected since the usage is equal to the limit of the `Alpha`. `Beta` can claim cores if they are released from `Alpha` or `Charlie`: -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha -> Beta; Alpha -> Charlie; @@ -464,10 +479,11 @@ cores in the tree will be rejected since the usage is equal to the limit of the Charlie [label="Charlie (u=6)", textcolor = "#00af00"]; } -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha -> Beta; Alpha -> Charlie; @@ -482,10 +498,11 @@ able to claim any more cores because the total usage of the tree is equal to the limit of `Alpha`, thus preventing `Charlie` from reclaiming the cores it had: -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha -> Beta; Alpha -> Charlie; @@ -502,10 +519,11 @@ is forbidden. Even though it is possible for the sum of all limits under limit of the parent would result in strange user experience and be misleading since the total usage of the tree would be capped at the limit of the parent: -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha -> Beta; Alpha -> Charlie; @@ -515,10 +533,11 @@ since the total usage of the tree would be capped at the limit of the parent: Charlie [label="Charlie (u=0)"]; } -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha -> Beta; Alpha -> Charlie; @@ -533,10 +552,11 @@ since the total usage of the tree would be capped at the limit of the parent: Finally, let's still assume the default registered limit for cores is 10, but we're going to create project `Alpha` with a limit of 6 cores. -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha; @@ -548,10 +568,11 @@ API ensures that project `Beta` doesn't assume the default of 10, despite the registered limit of 10 cores. Instead, the child assumes the parent's limit since no single child limit should exceed the limit of the parent: -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { orientation = portrait; + node [shape=box] Alpha -> Beta; @@ -562,9 +583,10 @@ since no single child limit should exceed the limit of the parent: This behavior is consistent regardless of the number of children added under project `Alpha`. -.. blockdiag:: +.. graphviz:: - blockdiag { + digraph { + node [shape=box] orientation = portrait; Alpha -> Beta; diff --git a/doc/source/conf.py b/doc/source/conf.py index 8ba1dadc51..0ea3c5b831 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -34,6 +34,7 @@ # ones. extensions = [ 'sphinx.ext.coverage', + 'sphinx.ext.graphviz', 'sphinx.ext.viewcode', 'sphinx.ext.todo', 'oslo_config.sphinxconfiggen', @@ -42,9 +43,7 @@ extensions = [ 'openstackdocstheme', 'oslo_policy.sphinxext', 'sphinxcontrib.apidoc', - 'sphinxcontrib.seqdiag', 'sphinx_feature_classification.support_matrix', - 'sphinxcontrib.blockdiag', ] blockdiag_html_image_format = 'SVG'