[{"data":1,"prerenderedAt":1203},["ShallowReactive",2],{"doc:\u002Fdocs\u002Fsso-keycloak":3,"docs-nav":982},{"id":4,"title":5,"body":6,"description":16,"extension":974,"meta":975,"navigation":977,"path":978,"seo":979,"stem":980,"__hash__":981},"docs\u002Fdocs\u002Fsso-keycloak.md","Single sign-on with Keycloak",{"type":7,"value":8,"toc":962},"minimark",[9,13,17,26,31,49,52,89,100,104,111,183,188,198,204,237,247,251,254,512,529,532,597,601,608,625,645,656,669,673,699,715,719,735,750,760,764,774,940,951,958],[10,11,5],"h1",{"id":12},"single-sign-on-with-keycloak",[14,15,16],"p",{},"How to let people sign in to Tabularia with your company's Keycloak, and have their\nKeycloak groups or roles decide what they see. For whoever installs Tabularia with the\nHelm chart and administers the Keycloak realm. Every step below was run against\nKeycloak 26 and the chart on a test cluster.",[14,18,19,20,25],{},"Signing in with an email and a password keeps working next to it, for the administrator\nthe chart creates: it is the way in when Keycloak is down. The other integrations are\nlisted in ",[21,22,24],"a",{"href":23},"\u002Fdocs\u002Fintegrations","Integrations",".",[27,28,30],"h2",{"id":29},"how-it-works","How it works",[32,33,34,43,46],"ol",{},[35,36,37,38,42],"li",{},"On the sign-in page a button (",[39,40,41],"em",{},"Sign in with …",") sends the browser to Keycloak.",[35,44,45],{},"Keycloak signs the person in, or recognises a session it already has, and sends the\nbrowser back to Tabularia's callback address with a one-time code.",[35,47,48],{},"Tabularia's gateway exchanges the code for the tokens, server to server, with the\nclient secret; checks them; creates the account at the first sign-in; and sets the\nperson's Tabularia groups from their Keycloak groups or roles.",[14,50,51],{},"Two things follow from it:",[53,54,55,74],"ul",{},[35,56,57,61,62,66,67,70,71,25],{},[58,59,60],"strong",{},"One address."," The sign-in has to start and end at the same address of Tabularia,\nthe one registered in Keycloak: a short-lived cookie set when it starts must come back\nwith the callback. People who open Tabularia at another name of the same server (an\nIP, an internal alias) or at ",[63,64,65],"code",{},"http"," instead of ",[63,68,69],{},"https"," fail with ",[63,72,73],{},"tx_missing",[35,75,76,79,80,83,84,88],{},[58,77,78],{},"The gateway talks to Keycloak."," It reads Keycloak's configuration, keys and tokens\nat the issuer address, the same one the browser uses: that address must be reachable\nfrom the cluster, and its certificate signed by an authority the gateway trusts — a\npublic one, or the company's own given in ",[63,81,82],{},"oidc.caCert","; or accepted on purpose\n(",[21,85,87],{"href":86},"#a-certificate-the-gateway-does-not-trust","a certificate the gateway does not trust",").",[14,90,91,92,95,96,99],{},"Below, Tabularia is at ",[63,93,94],{},"https:\u002F\u002Ftabularia.example.com"," and Keycloak's realm at\n",[63,97,98],{},"https:\u002F\u002Fsso.example.com\u002Frealms\u002Facme",": put your own addresses.",[27,101,103],{"id":102},"1-the-client-in-keycloak","1. The client in Keycloak",[14,105,106,107,110],{},"In the Keycloak admin console, realm ",[63,108,109],{},"acme",":",[32,112,113,130,145,166,174],{},[35,114,115,118,119,122,123,126,127],{},[58,116,117],{},"Clients → Create client."," Client type ",[39,120,121],{},"OpenID Connect",", Client ID ",[63,124,125],{},"tabularia",".\n",[58,128,129],{},"Next.",[35,131,132,135,136,139,140,126,143],{},[58,133,134],{},"Client authentication: On"," (a confidential client: the gateway keeps a secret).\nAuthentication flow: ",[58,137,138],{},"Standard flow"," only; turn off ",[39,141,142],{},"Direct access grants",[58,144,129],{},[35,146,147,150,151,154,155,158,159,162,163],{},[58,148,149],{},"Valid redirect URIs:"," ",[63,152,153],{},"https:\u002F\u002Ftabularia.example.com\u002Fapi\u002Fauth\u002Fsso\u002Fcallback",",\nexactly (scheme, host, ",[63,156,157],{},"\u002Fapi"," prefix, no trailing slash). ",[39,160,161],{},"Web origins"," can stay\nempty: the browser never calls Keycloak's APIs, the gateway does. ",[58,164,165],{},"Save.",[35,167,168,171,172],{},[58,169,170],{},"Advanced → Advanced settings → Proof Key for Code Exchange Code Challenge Method:\nS256."," Tabularia always sends it; this makes Keycloak require it. ",[58,173,165],{},[35,175,176,179,180,25],{},[58,177,178],{},"Credentials:"," copy the ",[58,181,182],{},"Client Secret",[184,185,187],"h3",{"id":186},"groups-or-roles","Groups or roles",[14,189,190,191,194,195,197],{},"Tabularia matches names: the Keycloak group or role ",[63,192,193],{},"finance"," lands the person in the\nTabularia group ",[63,196,193],{},". Choose what carries them.",[14,199,200,203],{},[58,201,202],{},"Keycloak groups"," (people are members of groups in the realm): give the client a\nmapper that puts them in the token.",[32,205,206,211],{},[35,207,208],{},[58,209,210],{},"Clients → tabularia → Client scopes → tabularia-dedicated → Configure a new mapper\n→ Group Membership.",[35,212,213,214,217,218,217,223,226,227,230,231,234,235],{},"Name ",[63,215,216],{},"groups",", ",[58,219,220,221],{},"Token Claim Name ",[63,222,216],{},[58,224,225],{},"Full group path: Off"," (otherwise a\nsubgroup arrives as ",[63,228,229],{},"parent\u002Ffinance","), ",[58,232,233],{},"Add to ID token: On",". ",[58,236,165],{},[14,238,239,242,243,246],{},[58,240,241],{},"Keycloak roles"," (realm roles or the client's own roles): nothing to add in Keycloak.\nIts default ",[39,244,245],{},"roles"," scope already puts them in the access token, where Tabularia reads\nthem; you only name their place in step 2.",[27,248,250],{"id":249},"2-tabularias-values","2. Tabularia's values",[14,252,253],{},"In the values file you install the chart with:",[255,256,261],"pre",{"className":257,"code":258,"language":259,"meta":260,"style":260},"language-yaml shiki shiki-themes github-dark-dimmed github-light","oidc:\n  enabled: true\n  issuer: \"https:\u002F\u002Fsso.example.com\u002Frealms\u002Facme\"\n  clientId: tabularia\n  redirectUri: \"https:\u002F\u002Ftabularia.example.com\u002Fapi\u002Fauth\u002Fsso\u002Fcallback\"\n  postLoginUrl: \"https:\u002F\u002Ftabularia.example.com\u002Fauth\u002Fcallback\"\n  buttonLabel: \"Sign in with ACME\"\n  # where the groups are (pick one, or several comma-separated):\n  #   groups                              Keycloak groups, with the mapper above\n  #   realm_access.roles                  realm roles\n  #   resource_access.tabularia.roles     roles of the client \"tabularia\"\n  groupsClaim: \"groups\"\n  # the Keycloak group (or role) whose members are Tabularia administrators\n  superuserGroup: \"tabularia-admins\"\n  # who may sign in at all: without these, anyone the realm authenticates gets an account\n  allowedEmailDomains: \"example.com\"\n  groupAllowlist: \"\"\n  requireAllowlistedGroup: false\n  # only when Keycloak's certificate is not signed by a public authority: see below\n  caCert: \"\"\n  caConfigMap: { name: \"\", key: \"ca.crt\" }\n  verifyTls: true\nsecrets:\n  oidcClientSecret: \"\u003Cthe Client Secret of step 1.5>\"\n","yaml","",[63,262,263,276,289,301,312,323,334,345,352,358,364,370,381,387,398,404,415,426,437,443,453,483,493,501],{"__ignoreMap":260},[264,265,268,272],"span",{"class":266,"line":267},"line",1,[264,269,271],{"class":270},"shb1k","oidc",[264,273,275],{"class":274},"ssh_m",":\n",[264,277,279,282,285],{"class":266,"line":278},2,[264,280,281],{"class":270},"  enabled",[264,283,284],{"class":274},": ",[264,286,288],{"class":287},"swcJU","true\n",[264,290,292,295,297],{"class":266,"line":291},3,[264,293,294],{"class":270},"  issuer",[264,296,284],{"class":274},[264,298,300],{"class":299},"sXfbr","\"https:\u002F\u002Fsso.example.com\u002Frealms\u002Facme\"\n",[264,302,304,307,309],{"class":266,"line":303},4,[264,305,306],{"class":270},"  clientId",[264,308,284],{"class":274},[264,310,311],{"class":299},"tabularia\n",[264,313,315,318,320],{"class":266,"line":314},5,[264,316,317],{"class":270},"  redirectUri",[264,319,284],{"class":274},[264,321,322],{"class":299},"\"https:\u002F\u002Ftabularia.example.com\u002Fapi\u002Fauth\u002Fsso\u002Fcallback\"\n",[264,324,326,329,331],{"class":266,"line":325},6,[264,327,328],{"class":270},"  postLoginUrl",[264,330,284],{"class":274},[264,332,333],{"class":299},"\"https:\u002F\u002Ftabularia.example.com\u002Fauth\u002Fcallback\"\n",[264,335,337,340,342],{"class":266,"line":336},7,[264,338,339],{"class":270},"  buttonLabel",[264,341,284],{"class":274},[264,343,344],{"class":299},"\"Sign in with ACME\"\n",[264,346,348],{"class":266,"line":347},8,[264,349,351],{"class":350},"sgHix","  # where the groups are (pick one, or several comma-separated):\n",[264,353,355],{"class":266,"line":354},9,[264,356,357],{"class":350},"  #   groups                              Keycloak groups, with the mapper above\n",[264,359,361],{"class":266,"line":360},10,[264,362,363],{"class":350},"  #   realm_access.roles                  realm roles\n",[264,365,367],{"class":266,"line":366},11,[264,368,369],{"class":350},"  #   resource_access.tabularia.roles     roles of the client \"tabularia\"\n",[264,371,373,376,378],{"class":266,"line":372},12,[264,374,375],{"class":270},"  groupsClaim",[264,377,284],{"class":274},[264,379,380],{"class":299},"\"groups\"\n",[264,382,384],{"class":266,"line":383},13,[264,385,386],{"class":350},"  # the Keycloak group (or role) whose members are Tabularia administrators\n",[264,388,390,393,395],{"class":266,"line":389},14,[264,391,392],{"class":270},"  superuserGroup",[264,394,284],{"class":274},[264,396,397],{"class":299},"\"tabularia-admins\"\n",[264,399,401],{"class":266,"line":400},15,[264,402,403],{"class":350},"  # who may sign in at all: without these, anyone the realm authenticates gets an account\n",[264,405,407,410,412],{"class":266,"line":406},16,[264,408,409],{"class":270},"  allowedEmailDomains",[264,411,284],{"class":274},[264,413,414],{"class":299},"\"example.com\"\n",[264,416,418,421,423],{"class":266,"line":417},17,[264,419,420],{"class":270},"  groupAllowlist",[264,422,284],{"class":274},[264,424,425],{"class":299},"\"\"\n",[264,427,429,432,434],{"class":266,"line":428},18,[264,430,431],{"class":270},"  requireAllowlistedGroup",[264,433,284],{"class":274},[264,435,436],{"class":287},"false\n",[264,438,440],{"class":266,"line":439},19,[264,441,442],{"class":350},"  # only when Keycloak's certificate is not signed by a public authority: see below\n",[264,444,446,449,451],{"class":266,"line":445},20,[264,447,448],{"class":270},"  caCert",[264,450,284],{"class":274},[264,452,425],{"class":299},[264,454,456,459,462,465,467,470,472,475,477,480],{"class":266,"line":455},21,[264,457,458],{"class":270},"  caConfigMap",[264,460,461],{"class":274},": { ",[264,463,464],{"class":270},"name",[264,466,284],{"class":274},[264,468,469],{"class":299},"\"\"",[264,471,217],{"class":274},[264,473,474],{"class":270},"key",[264,476,284],{"class":274},[264,478,479],{"class":299},"\"ca.crt\"",[264,481,482],{"class":274}," }\n",[264,484,486,489,491],{"class":266,"line":485},22,[264,487,488],{"class":270},"  verifyTls",[264,490,284],{"class":274},[264,492,288],{"class":287},[264,494,496,499],{"class":266,"line":495},23,[264,497,498],{"class":270},"secrets",[264,500,275],{"class":274},[264,502,504,507,509],{"class":266,"line":503},24,[264,505,506],{"class":270},"  oidcClientSecret",[264,508,284],{"class":274},[264,510,511],{"class":299},"\"\u003Cthe Client Secret of step 1.5>\"\n",[14,513,514,517,518,521,522,524,525,528],{},[63,515,516],{},"issuer"," is the realm's address as Keycloak publishes it: open\n",[63,519,520],{},"https:\u002F\u002Fsso.example.com\u002Frealms\u002Facme\u002F.well-known\u002Fopenid-configuration"," and copy its\n",[63,523,516],{}," field. If it shows an internal name instead, Keycloak's hostname setting\n(",[63,526,527],{},"KC_HOSTNAME",") is not the public address: fix that first, the browser and the gateway\nmust use the same one.",[14,530,531],{},"Apply it:",[255,533,537],{"className":534,"code":535,"language":536,"meta":260,"style":260},"language-bash shiki shiki-themes github-dark-dimmed github-light","helm upgrade \u003Crelease> infrastructure\u002Fhelm\u002Ftabularia --namespace \u003Cnamespace> \\\n  --values values-production.yaml --wait --timeout 20m\n","bash",[63,538,539,580],{"__ignoreMap":260},[264,540,541,545,548,552,555,558,561,564,567,569,572,574,576],{"class":266,"line":267},[264,542,544],{"class":543},"sqRhv","helm",[264,546,547],{"class":299}," upgrade",[264,549,551],{"class":550},"s6PUj"," \u003C",[264,553,554],{"class":299},"releas",[264,556,557],{"class":274},"e",[264,559,560],{"class":550},">",[264,562,563],{"class":299}," infrastructure\u002Fhelm\u002Ftabularia",[264,565,566],{"class":287}," --namespace",[264,568,551],{"class":550},[264,570,571],{"class":299},"namespac",[264,573,557],{"class":274},[264,575,560],{"class":550},[264,577,579],{"class":578},"sxsTv"," \\\n",[264,581,582,585,588,591,594],{"class":266,"line":278},[264,583,584],{"class":287},"  --values",[264,586,587],{"class":299}," values-production.yaml",[264,589,590],{"class":287}," --wait",[264,592,593],{"class":287}," --timeout",[264,595,596],{"class":299}," 20m\n",[184,598,600],{"id":599},"a-certificate-the-gateway-does-not-trust","A certificate the gateway does not trust",[14,602,603,604,607],{},"When Keycloak's certificate is signed by the company's own certificate authority (or is\nself-signed), the gateway does not trust it and every sign-in fails with\n",[63,605,606],{},"idp_untrusted",". Two ways out; the first is the one to prefer.",[14,609,610,613,614,617,618,620,621,624],{},[58,611,612],{},"Give the gateway the company's authority"," (",[63,615,616],{},"OIDC__CA_FILE",", chart ",[63,619,82],{}," or\n",[63,622,623],{},"oidc.caConfigMap","). From a file:",[255,626,628],{"className":534,"code":627,"language":536,"meta":260,"style":260},"helm upgrade … --set-file oidc.caCert=acme-root-ca.crt\n",[63,629,630],{"__ignoreMap":260},[264,631,632,634,636,639,642],{"class":266,"line":267},[264,633,544],{"class":543},[264,635,547],{"class":299},[264,637,638],{"class":299}," …",[264,640,641],{"class":287}," --set-file",[264,643,644],{"class":299}," oidc.caCert=acme-root-ca.crt\n",[14,646,647,648,651,652,655],{},"or name a ConfigMap that already holds it, such as a bundle trust-manager distributes:\n",[63,649,650],{},"oidc.caConfigMap: { name: acme-bundle, key: root-certs.pem }",". The public authorities\nstay trusted, and the certificate is still checked: only Keycloak's real one is\naccepted. To see which authority signed it:\n",[63,653,654],{},"openssl s_client -connect sso.example.com:443 -showcerts \u003C\u002Fdev\u002Fnull"," (the last\ncertificate of the chain, or its issuer).",[14,657,658,613,661,664,665,668],{},[58,659,660],{},"Accept the certificate without checking it",[63,662,663],{},"OIDC__VERIFY_TLS=false",", chart\n",[63,666,667],{},"oidc.verifyTls: false","), when the authority cannot be had. Whether to accept it is the\ninstaller's decision, and it has a cost: the gateway takes from Keycloak, on that same\nconnection, the keys it checks the sign-ins with. Whoever can stand between the cluster\nand Keycloak — a proxy, a tampered name server — can then hand it keys of their own and\nsign in as anyone, administrators included. Acceptable on a test cluster, or on a network\nwhere nobody else can reach that path; elsewhere give the authority instead. The gateway\nlogs a warning at every start while it is off, and a CA file given with it is not used.",[184,670,672],{"id":671},"the-groups-in-tabularia","The groups in Tabularia",[14,674,675,676,679,680,683,684,687,688,217,691,694,695,698],{},"Create, in ",[58,677,678],{},"Administration → Groups",", the groups whose names Keycloak sends, and\ngive them folders and permissions as for any group. A Keycloak group or role with no\nTabularia group of that name is ignored, unless ",[63,681,682],{},"oidc.autoCreateGroups"," is ",[63,685,686],{},"true",".\nKeycloak adds a few roles of its own (",[63,689,690],{},"offline_access",[63,692,693],{},"uma_authorization",",\n",[63,696,697],{},"default-roles-acme","): they match no group, and are ignored.",[14,700,701,702,706,707,710,711,25],{},"The full list of settings, with what each does: ",[21,703,705],{"href":704},"\u002Fdocs\u002Fconfiguration","configuration reference",",\nsection ",[39,708,709],{},"Single sign-on (OIDC)",". How names, administrators and observers are mapped, and\nwhy: ",[21,712,714],{"href":713},"\u002Fdocs\u002Fdesign\u002Fsso-group-mapping","SSO and group mapping",[27,716,718],{"id":717},"3-check","3. Check",[255,720,722],{"className":534,"code":721,"language":536,"meta":260,"style":260},"curl -s https:\u002F\u002Ftabularia.example.com\u002Fapi\u002Fauth\u002Fsso\u002Fconfig\n",[63,723,724],{"__ignoreMap":260},[264,725,726,729,732],{"class":266,"line":267},[264,727,728],{"class":543},"curl",[264,730,731],{"class":287}," -s",[264,733,734],{"class":299}," https:\u002F\u002Ftabularia.example.com\u002Fapi\u002Fauth\u002Fsso\u002Fconfig\n",[14,736,737,738,741,742,745,746,749],{},"answers ",[63,739,740],{},"{\"enabled\":true,\"button_label\":\"Sign in with ACME\"}",". Sign in with a person of\nthe realm: Tabularia opens, signed in. In ",[58,743,744],{},"Audit"," the sign-in is there (",[39,747,748],{},"SSO login","),\nwith the groups it received, added and removed.",[14,751,752,753,756,757,25],{},"From then on every sign-in sets the groups again (",[63,754,755],{},"oidc.authoritative",", on by default):\nsomeone removed from a Keycloak group loses the Tabularia group at the next sign-in.\nAdministrator and observer groups made in Tabularia are never changed by Keycloak; an\nadministrator comes from Keycloak only through ",[63,758,759],{},"oidc.superuserGroup",[27,761,763],{"id":762},"when-it-fails","When it fails",[14,765,766,767,770,771,88],{},"A failed sign-in comes back to Tabularia's sign-in page with a short explanation and a\ncode. The gateway's log has the whole reason (",[63,768,769],{},"kubectl logs deployment\u002F\u003Crelease>-tabularia-gateway","),\nand the audit log the code (",[39,772,773],{},"Failed SSO login",[775,776,777,790],"table",{},[778,779,780],"thead",{},[781,782,783,787],"tr",{},[784,785,786],"th",{},"Code",[784,788,789],{},"What it means, what to do",[791,792,793,813,827,837,850,860,880,894,904,918],"tbody",{},[781,794,795,800],{},[796,797,798],"td",{},[63,799,73],{},[796,801,802,803,805,806,808,809,812],{},"The sign-in came back without its cookie: it started at an address other than the redirect URI's (another name, an IP, ",[63,804,65],{}," against ",[63,807,69],{},"). The gateway logs ",[63,810,811],{},"sign-in started at \u003Caddress> but OIDC__REDIRECT_URI is on \u003Caddress>"," when it begins. Have people open Tabularia at the redirect URI's address, or register theirs too",[781,814,815,820],{},[796,816,817],{},[63,818,819],{},"tx_expired",[796,821,822,823,826],{},"Longer than 10 minutes between the click and the return (",[63,824,825],{},"OIDC__LOGIN_TX_TTL_SECONDS",": a first sign-in that asks to set a password or a second factor can take that), or the clocks of the cluster's nodes more than a minute apart: check NTP",[781,828,829,834],{},[796,830,831],{},[63,832,833],{},"state_mismatch",[796,835,836],{},"The answer is not for this sign-in (an old or altered link): start again. Two sign-ins at once — a double click, a second tab — are fine",[781,838,839,844],{},[796,840,841],{},[63,842,843],{},"token_invalid",[796,845,846,847,849],{},"The id_token did not pass the checks. Usually Keycloak's clock more than a minute apart from the cluster's (check NTP on both), or an ",[63,848,516],{}," that differs from the one Keycloak signs with",[781,851,852,857],{},[796,853,854],{},[63,855,856],{},"code_rejected",[796,858,859],{},"Keycloak refused to exchange the code: a wrong client secret, or a redirect URI not identical to the registered one",[781,861,862,866],{},[796,863,864],{},[63,865,606],{},[796,867,868,869,871,872,874,875,613,877,879],{},"Keycloak's certificate is signed by an authority the gateway does not trust: a company one, or none. Give the authority in ",[63,870,82],{}," or ",[63,873,623],{},", or accept the certificate with ",[63,876,667],{},[21,878,87],{"href":86},"). The gateway log names the host and the reason",[781,881,882,887],{},[796,883,884],{},[63,885,886],{},"idp_unreachable",[796,888,889,890,893],{},"The gateway cannot reach Keycloak at the issuer address: name resolution or network policy from the cluster (with ",[63,891,892],{},"networkPolicy.egressCIDRs"," restricted, allow Keycloak's address)",[781,895,896,901],{},[796,897,898],{},[63,899,900],{},"idp_denied",[796,902,903],{},"Keycloak did not let the person in (disabled account, a required action refused)",[781,905,906,911],{},[796,907,908],{},[63,909,910],{},"email_unverified",[796,912,913,914,917],{},"An account with that e-mail exists in Tabularia and Keycloak does not say the address is verified: tick ",[39,915,916],{},"Email verified"," on the user, or have them verify it",[781,919,920,925],{},[796,921,922],{},[63,923,924],{},"not_allowed",[796,926,927,928,931,932,935,936,939],{},"The person is outside ",[63,929,930],{},"allowedEmailDomains",", or without a group of ",[63,933,934],{},"groupAllowlist"," while ",[63,937,938],{},"requireAllowlistedGroup"," is on",[14,941,942,943,946,947,950],{},"A person who signs in but lands without groups: their token does not carry them. Check\nthe mapper (step 1, ",[39,944,945],{},"Add to ID token"," on) or the place named in ",[63,948,949],{},"groupsClaim","; the audit\nentry of the sign-in lists the groups received.",[14,952,953,954,25],{},"A runnable example with a ready realm, to try all this on a laptop with Docker Compose:\n",[21,955,957],{"href":956},"\u002Fdocs\u002Fexamples\u002Fkeycloak","Keycloak example",[959,960,961],"style",{},"html pre.shiki code .shb1k, html code.shiki .shb1k{--shiki-default:#8DDB8C;--shiki-light:#22863A}html pre.shiki code .ssh_m, html code.shiki .ssh_m{--shiki-default:#ADBAC7;--shiki-light:#24292E}html pre.shiki code .swcJU, html code.shiki .swcJU{--shiki-default:#6CB6FF;--shiki-light:#005CC5}html pre.shiki code .sXfbr, html code.shiki .sXfbr{--shiki-default:#96D0FF;--shiki-light:#032F62}html pre.shiki code .sgHix, html code.shiki .sgHix{--shiki-default:#768390;--shiki-light:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .sqRhv, html code.shiki .sqRhv{--shiki-default:#F69D50;--shiki-light:#6F42C1}html pre.shiki code .s6PUj, html code.shiki .s6PUj{--shiki-default:#F47067;--shiki-light:#D73A49}html pre.shiki code .sxsTv, html code.shiki .sxsTv{--shiki-default:#F47067;--shiki-light:#005CC5}",{"title":260,"searchDepth":278,"depth":291,"links":963},[964,965,968,972,973],{"id":29,"depth":278,"text":30},{"id":102,"depth":278,"text":103,"children":966},[967],{"id":186,"depth":291,"text":187},{"id":249,"depth":278,"text":250,"children":969},[970,971],{"id":599,"depth":291,"text":600},{"id":671,"depth":291,"text":672},{"id":717,"depth":278,"text":718},{"id":762,"depth":278,"text":763},"md",{"source":976},"docs\u002Fsso-keycloak.md",true,"\u002Fdocs\u002Fsso-keycloak",{"title":5,"description":16},"docs\u002Fsso-keycloak","FiK_ORx_5KFndz_74ry6WkpxA4jLzz72LQVlDA__HI4",[983,1002,1058,1090,1122,1147,1170,1195],{"title":984,"icon":985,"intro":986,"items":987},"Getting started","start","Install it on one machine, learn the few ideas everything else is built on, and run a first flow.",[988,990,993,996,999],{"path":989,"title":984},"\u002Fdocs\u002Fgetting-started",{"path":991,"title":992},"\u002Fdocs\u002Fgetting-started-docker-compose","Walkthrough with Docker Compose",{"path":994,"title":995},"\u002Fdocs\u002Fdeploy\u002Fminikube","Try it on your computer (minikube)",{"path":997,"title":998},"\u002Fdocs\u002Fconcepts","Concepts",{"path":1000,"title":1001},"\u002Fdocs\u002Foverview","Overview (README)",{"title":1003,"icon":1004,"intro":1005,"items":1006},"Capabilities","capabilities","What you can build: visual flows, analysis on any step, AI questions, pixel-perfect reports, dashboards, Python, R and SQL scripts, containers and the orchestration that ties them together.",[1007,1010,1013,1016,1019,1022,1025,1028,1031,1034,1037,1040,1043,1046,1049,1052,1055],{"path":1008,"title":1009},"\u002Fdocs\u002Fcapabilities","All capabilities",{"path":1011,"title":1012},"\u002Fdocs\u002Fcapabilities\u002Fflows","Low-code flows",{"path":1014,"title":1015},"\u002Fdocs\u002Fcapabilities\u002Fpreview-and-analysis","Preview and analysis",{"path":1017,"title":1018},"\u002Fdocs\u002Fcapabilities\u002Fask-your-data","Ask your data with AI",{"path":1020,"title":1021},"\u002Fdocs\u002Fcapabilities\u002Freports","Pixel-perfect reports",{"path":1023,"title":1024},"\u002Fdocs\u002Fcapabilities\u002Freport-functions","Report functions",{"path":1026,"title":1027},"\u002Fdocs\u002Fcapabilities\u002Fdashboards","Dashboards",{"path":1029,"title":1030},"\u002Fdocs\u002Fscripting","Python and R",{"path":1032,"title":1033},"\u002Fdocs\u002Fsql-scripts","SQL scripts",{"path":1035,"title":1036},"\u002Fdocs\u002Fcapabilities\u002Fnotebooks","marimo notebooks as apps",{"path":1038,"title":1039},"\u002Fdocs\u002Fcontainer-node","Docker images in flows",{"path":1041,"title":1042},"\u002Fdocs\u002Fcapabilities\u002Fflow-groups","Flow groups: orchestration",{"path":1044,"title":1045},"\u002Fdocs\u002Fcapabilities\u002Fscheduling","Scheduling",{"path":1047,"title":1048},"\u002Fdocs\u002Fpushmail","Pushmail: datasources, reports and dashboards by email",{"path":1050,"title":1051},"\u002Fdocs\u002Fplaceholders","Dynamic dates",{"path":1053,"title":1054},"\u002Fdocs\u002Fcapabilities\u002Fengines","Engines",{"path":1056,"title":1057},"\u002Fdocs\u002Fengines\u002Fengine-differences","Engine differences",{"title":1059,"icon":1060,"intro":1061,"items":1062},"Sources","sources","Every way data comes in: files, databases, buckets, SFTP folders, HTTP APIs, SharePoint, Google Sheets, and the results of your own flows.",[1063,1066,1069,1072,1075,1078,1081,1084,1087],{"path":1064,"title":1065},"\u002Fdocs\u002Fsources","All sources",{"path":1067,"title":1068},"\u002Fdocs\u002Fsources\u002Ffiles","Uploaded files",{"path":1070,"title":1071},"\u002Fdocs\u002Fsources\u002Fdatabases","Database tables and queries",{"path":1073,"title":1074},"\u002Fdocs\u002Fs3-datasource","S3 buckets",{"path":1076,"title":1077},"\u002Fdocs\u002Fsftp","SFTP folders",{"path":1079,"title":1080},"\u002Fdocs\u002Fapi-datasource","HTTP APIs",{"path":1082,"title":1083},"\u002Fdocs\u002Fsources\u002Fsharepoint","Excel on SharePoint",{"path":1085,"title":1086},"\u002Fdocs\u002Fsources\u002Fgoogle-sheets","Google Sheets",{"path":1088,"title":1089},"\u002Fdocs\u002Fsources\u002Fflow-outputs","Results of flows and scripts",{"title":1091,"icon":1092,"intro":1093,"items":1094},"Connections","connections","The systems Tabularia reads from and writes to, how each connection is set up, and who may use it.",[1095,1098,1101,1104,1107,1109,1110,1112,1113,1116,1119],{"path":1096,"title":1097},"\u002Fdocs\u002Fconnections","All connections",{"path":1099,"title":1100},"\u002Fdocs\u002Fconnections\u002Fdatabases","Databases",{"path":1102,"title":1103},"\u002Fdocs\u002Fsqlserver","Microsoft SQL Server driver",{"path":1105,"title":1106},"\u002Fdocs\u002Fconnections\u002Fs3","S3 and compatible storage",{"path":1076,"title":1108},"SFTP servers",{"path":1079,"title":1080},{"path":1082,"title":1111},"SharePoint",{"path":1085,"title":1086},{"path":1114,"title":1115},"\u002Fdocs\u002Fconnections\u002Fsmtp","SMTP email",{"path":1117,"title":1118},"\u002Fdocs\u002Fcontainer-registries","Container registries",{"path":1120,"title":1121},"\u002Fdocs\u002Fconnections\u002Fdestinations","Writing data out",{"title":24,"icon":1123,"intro":1124,"items":1125},"integrations","How Tabularia fits your stack: AI providers, the REST API and webhooks, dbt, OpenLineage, single sign-on, SIEM and monitoring.",[1126,1128,1131,1134,1137,1140,1141,1144],{"path":23,"title":1127},"All integrations",{"path":1129,"title":1130},"\u002Fdocs\u002Fintegrations\u002Fai","AI: where it is used",{"path":1132,"title":1133},"\u002Fdocs\u002Fapi","API, tokens and webhooks",{"path":1135,"title":1136},"\u002Fdocs\u002Fdbt-export","Export to dbt",{"path":1138,"title":1139},"\u002Fdocs\u002Fopenlineage","OpenLineage",{"path":978,"title":5},{"path":1142,"title":1143},"\u002Fdocs\u002Fsiem-splunk","Audit log to Splunk",{"path":1145,"title":1146},"\u002Fdocs\u002Fintegrations\u002Fmonitoring","Monitoring",{"title":1148,"icon":1149,"intro":1150,"items":1151},"Security & governance","governance","Who may see and do what, what is recorded, and how data quality and personal data are kept in check.",[1152,1155,1158,1161,1164,1165,1167],{"path":1153,"title":1154},"\u002Fdocs\u002Fgovernance","Overview",{"path":1156,"title":1157},"\u002Fdocs\u002Fgovernance\u002Fpermissions","Permissions",{"path":1159,"title":1160},"\u002Fdocs\u002Fgovernance\u002Faudit-log","Audit log",{"path":1162,"title":1163},"\u002Fdocs\u002Fgovernance\u002Fdata-contracts","Data contracts",{"path":713,"title":714},{"path":956,"title":1166},"Keycloak example on a laptop",{"path":1168,"title":1169},"\u002Fdocs\u002Fengines\u002Fclickhouse-ai-user","A ClickHouse user for the AI assistant",{"title":1171,"icon":1172,"intro":1173,"items":1174},"Install & operate","operate","Run it in production on Kubernetes, configure it, upgrade it and keep it healthy.",[1175,1178,1181,1184,1187,1189,1192],{"path":1176,"title":1177},"\u002Fdocs\u002Fdeploy\u002Fkubernetes","Kubernetes and Helm",{"path":1179,"title":1180},"\u002Fdocs\u002Fdeploy\u002Fkind","A company-like cluster on one computer (kind)",{"path":1182,"title":1183},"\u002Fdocs\u002Fdeploy\u002Fupgrading","Upgrading, backups and restore",{"path":1185,"title":1186},"\u002Fdocs\u002Fdeploy\u002Frelease-checklist","Install and upgrade checklist",{"path":704,"title":1188},"Configuration reference",{"path":1190,"title":1191},"\u002Fdocs\u002Fengine-settings","Settings from the panel",{"path":1193,"title":1194},"\u002Fdocs\u002Fdatabase-migrations","Database migrations",{"title":1196,"icon":1197,"intro":1198,"items":1199},"Reference","reference","Release process and the remaining reference pages.",[1200],{"path":1201,"title":1202},"\u002Fdocs\u002Frelease-process","Release process",1793656372089]