├── .env ├── .gitignore ├── README.adoc ├── build └── postgres-hypopg │ ├── Dockerfile │ └── build.sh ├── cert ├── .gitignore └── cert-local.sh ├── config ├── keycloak-realm-config │ ├── master-realm.json │ └── master-users-0.json ├── keycloak.env ├── postgres.env └── traefik │ ├── dynamic_conf.toml │ ├── forward.ini │ └── traefik.toml ├── docker-compose.yml └── update-domain.sh /.env: -------------------------------------------------------------------------------- 1 | DOMAIN=localtest.me 2 | COMPOSE_PROJECT_NAME=test 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .data 2 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | # Traefik Keycloak SSO Auth reverse proxy Template 2 | 3 | Good title. I like it. It means nothing, and it means everything if you wanna deploy some fancy services together on the internet with some kind of security. 4 | 5 | Maybe it seems difficult. Because it's difficult. I've spent lot of hour to make it happen, so I decided to share it. I found tons of examples for the isolated parts of features, but nothing that complete. Maybe it's interesting, but it's not if you prefer some out of box kubernetes solution or wanna pay tons of money for it as a service. 6 | 7 | As a matter of fact I'm a lazy fat poor guy who don't wanna pay for every service for that very rich corporations, which rip off me for relatively basic things. Yeah I know, they are the masters of the universe, and forget everything, they will care for you a lot! If you haven't see tha movie https://www.imdb.com/title/tt9893250/[I care a lot] I recommend it :) 8 | 9 | Another reason is sometimes there are some clients whoes does not wanna risk their 10 | data to travel over public internet, they have their infrustructure and they are happy with it - so there is no GCloud, Amazon, Azure. No cloud, no rain, and we know the water can do some serious demage wih electronic devices. 11 | Is this a problem? Not at all! Kubernetes, docker, rancher can be executed in bare metal. Cool. 12 | 13 | Most of the applications doesn't need to serve the whole china's populations, or no need for big cloud clusters or low latency reactive streams. Maybe you are a simple sista/bro as me who only want to deploy some boring business service, which helps to buy gadgets and food for amily. 14 | My very big problem is nobody pays for https://en.wikipedia.org/wiki/%22Hello,_World!%22_program[hello world] or https://www.oracle.com/java/technologies/petstore-v1312.html[pet store] so I have to develop a little more diffcult applications than that. But fortunatelly, not a complete datacenter or NASA satelite tracking solution I have to make. 15 | I'm lazy, so we are developing some very sophisticated modelling stuff called https://judo.codes[JUDO] in our company. I hope in the future Gartner's prediction will become true - and I haven't do the boring programming stuffz. I wanna draw. Boxes! Lines and boxes! I can draw boxes in modeller! Everybody like boxes! And the application is writing itself, and no other thing really matters! :) So I can deploy my boxes and I can login with my facebook within it! Oh yeah, my karma is complete! 16 | 17 | Ready to learn new things. It's good isn't it? 18 | 19 | I will provide an example, how to setup a docker-compose stack that provides a Traefik instance to reverse proxy services with SSO authorized routes inside a docker stack. Huh, long sentence. This very long title appear again in a little bit longer sentence. 20 | 21 | The SSO using an embedded keycloak IDM server that let makes it happen. It authenticates you, checking your ID card, photo, retina and brainwaves. 22 | If you set up, checking your multi x-factor identity. It can be configured as an Enterprise (sic) radius based SSO solution. (yeah I 23 | know it's not fancy in this cloud world) 24 | 25 | So it can be used to limit access for administrative sites, statistical services, databases etc. So you are in your safe garden! 26 | So you can start to concentrate your important work, your Springboot, JHypster, Karaf or Microprofile applications. Or not. Maybe you will use PHP, Python or NodeJS app - Sorry for the incomplete list. Don't care, I'm not always pool correct. But good news. Every platform 27 | gives tons of examples how to use your current token! 28 | 29 | Keycloak have tons of options which can be done wrong, so you can set to use google or any other OpenID authentication provider. (over keycloak, or auth forwarder itself, your choice, your life) As the project name suggests it helps to solve complicated time consuming settings and trials for a deployment solutions. The main goal is not a full production ready solution - but because maybe I've already mention I'm a big fat lazy guy, so I will do some settings change and deploy it to a bare metal which cannot be accessed from outside world, so there is no risk :)- So this all stuff has educational purposes and make good (or not to good) example of materializing different standards as a system. So you are allow to borrow any part of. I've also done that, just put pieces together a little bit different. 30 | 31 | My goal is to make compact and reusable solution for multi service containers, every container have own subdomains and HTTP ports and can be addressed with the container'name as subdomain - I'm lazy to remember ports and IP's. 32 | 33 | It is made with docker-compose and not for kubernetes, because it's not about containers itself. Kubernetes is cool stuff if you are making High Availablity performant systems (and need several instances to process workloads), but I'm getting my payment for not that large applications. 34 | 35 | My assumption if you read that title and you are reading this lines (my condolences), you are confident about docker and you have your 5 cents about it. If not, it's the time. Without container the life is harder. I'm fat lazy old guy, but the best part of IT industry in the last 20 years is the containers. 36 | 37 | So too much of letters. Every body likes boxes! So here it is: 38 | 39 | 40 | [ditaa] 41 | ---- 42 | +-------------------------+ 43 | +--+ unsecured.localtest.me | 44 | | +------------+------------+ 45 | | ^ +-----------------------------------------+ 46 | +----------+ v | | authenticated | 47 | | Client 1 |----+ | | | 48 | +----------+ | | v | 49 | | +--------+--------+ +--------+--------+ +----------+----------+ 50 | +----------+ | | Reverse proxy | | Auth forwarder | not authenticated | IDM (Keycloak) | 51 | | Client 2 |----+----->| (Traefik) +------>+ (traefik AF) +-------------------->+ | 52 | +----------+ | | *.localtest.me | |auth.localtest.me| |keycloak.localtest.me| 53 | | +--------+--------+ +--------+--------+ +----------+----------+ 54 | +----------+ | | 55 | | Client 3 |----+ | authenticated 56 | +----------+ ^ v 57 | | +------------+----------+ 58 | +----------------------------| secured.localtest.me | 59 | +-----------------------+ 60 | ---- 61 | 62 | 63 | My boss (no, not the God), always says if there is not a command which can be executed immediately to collect the easy success, 64 | you will be bored and will not read all of this very exciting documentation. So the other very important stuff is here. 65 | 66 | Meybe I forgot to mention, but `docker` and `docker-compose` have to be installed. And because the 80 and 443 port are below 1000, 67 | some system allow only to run it with root access. If you are not your system's God, or don't wanna be, you have to edit the 68 | config files a little bit and set ports to upper region. On that case please read the boring configuration documentations 69 | before run. But maybe docker daemon is your hardwre's God. On that case don't panic, just type. 70 | 71 | This template is executable, can be run with: 72 | 73 | . Create certificate 74 | + 75 | -- 76 | ``` 77 | cd cert 78 | ./cert-local.sh 79 | ``` 80 | It create seld-signed wildcard domain certificate, which valid for `*.localtest.me`. It have to be done one time, after this the certificate will 81 | serve you for 2 years and 30 days. Just enough time to forget about it, and forget how to recreate. 82 | Nice domain, localtest.me. Its proactive, you wanna tests it, it asks for it :) So guys, I love you, thanks. https://readme.localtest.me/[localtest.me] 83 | 84 | -- 85 | + 86 | . Import the CA into OS keychain. Usally double click in the `minica.pem` is enough. 87 | You can also play commands listed below for Debian/Ubuntu based OS: 88 | 89 | **System** 90 | 91 | Install the root certifcate on your system 92 | ``` 93 | sudo cp ./cert/minica.pem /usr/local/share/ca-certificates/minica.crt 94 | sudo chmod 644 /usr/local/share/ca-certificates/minica.crt 95 | sudo update-ca-certificates 96 | ``` 97 | 98 | But its OS and Browser dependent. When this CA is imported all other 99 | generated certificate in browser will be valid. Or always fight with your browser that you woluld like to proceed to site. 100 | 101 | **Browser (Firefox, Chromium,...)** 102 | 103 | Linux doesn't have a Trustore unlike Mac. 104 | 105 | Instead of adding the certificate manually for each application lazy developers use a script. 106 | 107 | First install the `certutil` tool. 108 | 109 | ``` 110 | sudo apt install libnss3-tools 111 | ``` 112 | 113 | This scripts finds trust store databases and imports the new root certificate into them. 114 | ``` 115 | #!/bin/sh 116 | 117 | ### Script installs minica.pem to certificate trust store of applications using NSS 118 | ### (e.g. Firefox, Thunderbird, Chromium) 119 | ### Mozilla uses cert8, Chromium and Chrome use cert9 120 | 121 | ### 122 | ### Requirement: apt install libnss3-tools 123 | ### 124 | 125 | 126 | ### 127 | ### CA file to install (customize!) 128 | ### Retrieve Certname: openssl x509 -noout -subject -in minica.pem 129 | ### 130 | 131 | certfile="minica.pem" 132 | certname="minica root ca" 133 | 134 | 135 | 136 | ### 137 | ### For cert8 (legacy - DBM) 138 | ### 139 | 140 | for certDB in $(find ~/ -name "cert8.db") 141 | do 142 | certdir=$(dirname ${certDB}); 143 | certutil -A -n "${certname}" -t "TCu,Cu,Tu" -i ${certfile} -d dbm:${certdir} 144 | done 145 | 146 | 147 | ### 148 | ### For cert9 (SQL) 149 | ### 150 | 151 | for certDB in $(find ~/ -name "cert9.db") 152 | do 153 | certdir=$(dirname ${certDB}); 154 | certutil -A -n "${certname}" -t "TCu,Cu,Tu" -i ${certfile} -d sql:${certdir} 155 | done 156 | 157 | ``` 158 | 159 | Restart your browsers. Your certificates are now trusted. 160 | Source: https://gist.github.com/mwidmann/115c2a7059dcce300b61f625d887e5dc 161 | 162 | . Start compose 163 | + 164 | -- 165 | ``` 166 | docker-compose up 167 | ``` 168 | -- 169 | 170 | Okay... and what. Patitence. Eventually it will finish the job and starts. When its ready, you can test the setup with: 171 | 172 | https://whoami.localtest.me 173 | 174 | 175 | The user is admin@example.com and the password is `password`. Yes. Its true. The top star password is used as password. 176 | Totally unsecure. Just to feel uncomfortable enough to change it immediately. So please change it in keycloak. I beg you. 177 | 178 | You think you will see some very interesting thing... Huh. no... Iw will no some some kitty or playing bears. It will only dipslay 179 | your boring request details. 180 | 181 | But the important thing you are logged in. There is a side effect of that: sometimes you wanna leave. 182 | You have the sword, any subdomain can accep the `/_oauth/logout` - and your keys are droped to the ocean, 183 | and your are fired! 184 | 185 | 186 | ### Some explanation - what the heck is this? 187 | 188 | There is a https://www.youtube.com/watch?v=xAkHiAqtunQ&ab_channel=5700102z[whoami] named service which is exposed as https://whoami.localtest.me . The container can be accessed with authentication 189 | only, so the site redirected to https://keycloak.localtest.me and after a successfull authentication the whoami container is accessible over https. Sound easy right? Not at all :) To un derstand how it works some explanation is required. 190 | 191 | 192 | ### The reverse proxy 193 | 194 | Reverse. What? I have a keyhole and an address and I can access a lot of services without knowing where they are and how. So cool. I must not know every single port number, IP's and other boring details. See the boxes! The flow is there! So time for some professional grade text. 195 | 196 | The term reverse proxy (see: Load Balancer) is normally applied to a service that sits in front of one or more servers (such as a webserver), accepting requests from clients for resources located on the server(s) - so kitty picture can travel over the wire with lightnig speed. From the client point of view, the reverse proxy appears to be the web server and so is totally transparent to the remote user. In our case thare is services inside the compose containers 197 | which can be accessed over a subdomain (or context path. Your choice, your life. But be carefull, lot of fancy client technologies 198 | - without any names, khmm - don't care and wanna get the whole root path). 199 | 200 | 201 | ### OpenID connect 202 | 203 | Yeah! It is baby! I have facebook, google, github, so I have a tons of OpenID auth provider and 204 | Identity manager - like facebook, they KNOW me - better than me - and I'm the person and I can have access to my very own systems. 205 | 206 | OpenID Connect is a simple identity layer on top of the OAuth 2.0 protocol, which allows computing clients to verify the identity of an end-user based on the authentication performed by an authorization server, as well as to obtain basic profile information about the end-user in an interoperable and REST-like manner. In technical terms, OpenID Connect specifies a RESTful HTTP API, using JSON as a data format. 207 | 208 | OpenID Connect allows a range of kinds of clients, including Web-based, mobile, and JavaScript clients, to request and receive information about authenticated sessions and end-users. The specification suite is extensible, supporting optional features such as encryption of identity data, discovery of OpenID Providers, and session management. Yes, that whole stuff needed to be able to login one time and later my every service can recognize me over my browser session and accept my identity. 209 | 210 | ### X509 Certificates 211 | 212 | Nice that we have a HTTP protocol to communicate with servers. But how can be it secure enough to protect our digital freedom? 213 | The better question is if I store my user's name in a Keycloak server what part of GDPR I violate? Do you know? Or do you have your own Dr. Gonzo to help find your legal way? 214 | 215 | In cryptography, X.509 is a standard defining the format of public key certificates. X.509 certificates are used in many Internet protocols, including TLS/SSL, which is the basis for HTTPS, the secure protocol for browsing the web. They are also used in offline applications, like electronic signatures. An X.509 certificate contains a public key and an identity (a hostname, or an organization, or an individual), and is either signed by a certificate authority or self-signed - as in our test case. When a certificate is signed by a trusted certificate authority, or validated by other means, someone holding that certificate can rely on the public key it contains to establish secure communications with another party, or validate documents digitally signed by the corresponding private key. Huh, whatever. My browser crying their eyes out if I haven't got one valid, so better to have one. And it is 21th century. In my smart watch (if sombody knows me knows I'm lying now - because I don't have one) I have enough horse power to be able to forget clear text. Clear 216 | text is not fancy like clean coding. 217 | 218 | ### Single sing-on (SSO - not https://www.youtube.com/watch?v=cvChjHcABPA&ab_channel=AbbaVEVO[S.O.S] - maybe you are old enogh as me to know ABBA) 219 | 220 | It's can be cool if any service inside or a slice of container universe can be accessed after a successful authentication, right? 221 | Single sign-on (SSO) is an authentication scheme that allows a user to log in with a single ID and password to any of several related, yet independent, software systems. True single sign-on allows the user to log in once and access services without re-entering authentication factors. We are lazy enough to type password more than once? Isn't it? 222 | 223 | 224 | ### https://www.youtube.com/watch?v=al_CT788Ry4&ab_channel=SesameStreet[Cookies] 225 | 226 | Yeah! Cookies. In this side of world everybody got cookies, so we know well. Or doesn't? This cookie is not for humans I'm speaking of. It's for 227 | browsers. Some pieace of information which are attached to every request-response to be able to track conversation between server and client. 228 | 229 | An HTTP cookie (also called web cookie, Internet cookie, browser cookie, or simply cookie) is a small piece of data stored on the user's computer by the web browser while browsing a website. Cookies were designed to be a reliable mechanism for websites to remember stateful information (such as items added in the shopping cart in an online store) or to record the user's browsing activity (including clicking particular buttons, logging in, or recording which pages were visited in the past). They can also be used to remember pieces of information that the user previously entered into form fields, such as names, addresses, passwords, and payment card numbers. 230 | 231 | Yes, my friend, corporations also plant cookies in your browser to track you down and sell you a lot of things which is totally garbages and 232 | you don't really need. For us it have other purpose. To store your key which was legally created after your succesfull login attempt. 233 | 234 | 235 | ## Configuration 236 | 237 | So, you are the AFAB/Agender/Aliagender/AMAB/Androgyne/Aporagender/Bigender/Binarism/Body dysphoria 238 | /Boi/Butch/Cisgender/Cisnormativity/Cissexism/Demiboy/Demigender/Demigirl/Dyadic/Feminine-of-center 239 | /Feminine-presenting/Girl/Guy, who thinks differently and the default given template isnn't enough good for you. 240 | Oh. Okay. Maybe. Let's do it. 241 | 242 | 243 | ### .env file 244 | 245 | It's goal to store every environmental parameters. So we are storing there our network and domain name now. But! It's for 246 | `docker-compose.yaml` only. There are other configurations which referencing the domain name. So it's the best if you list it 247 | and change it. (or using the fency https://en.wikipedia.org/wiki/Sed[sed] based find and replace tool from 1973. Thank you Mr. Lee E. MacMahon) 248 | 249 | ``` 250 | ./update-domain.sh example.com 251 | ``` 252 | 253 | It replace the original domain defined in .env file in all files where it's defined. I'm lazy again. It's boring. I would like to draw boxes. Don't forget the certification generator is another script, so when the domain changed, please change it! 254 | 255 | 256 | ## Create certificates 257 | 258 | The whole solution uses certifications. Imagine a certification is a box of key :) yeah, boxes. The `cert` directory contains a https://github.com/jsha/minica[minica] docker based script to create self 259 | signed wildcard domain SSL cert by default. 260 | 261 | Wildcard cert means there is one key rule every key. It will be valid for every subdomain in your domain. Fine yeah cool. 262 | But if you like to create keys or you are a poor bastard who haven't got tons of money. Hmmm. Interesting. It's https://comodosslstore.com/promoads/positivewildcardssl.aspx?gclid=Cj0KCQjwjPaCBhDkARIsAISZN7RUjJKJRMIyDRMGQw45KCHfBxBNVDA_Se9hV5iJcs_pkdKkCQWT5r4aAmTXEALw_wcB[cheaper] than expected now. Okay go and buy one and put it into `cert/ _.` directory. 263 | 264 | If you wanna create `./cert-local.sh` script contains example how to generate self signed wildcard domain CA's. 265 | 266 | Another solution is https://letsencrypt.org/[Let's encrypt]. The traefik supports it with certbot renewal. What the hack is Let's encrypt? 267 | Imagine a world in the past, where developers do not wanna pay certification taxes to very-sign and comodo for 268 | every pages. That was the golden age of the plain text http. With some middle man attack or with some server with promicious mode ethernet card can collect tons of password in a sec. Ooo, I miss it :) But some companies does not like 269 | that constantly have problems, everybody have security problems and always waiting for solutions from service providers and browsers. 270 | The problem cannot solved by them. So they decided that making some service which is free and everybody can get full valid certification - not some self signed one. So the https://www.imdb.com/title/tt0120737/[Fellowship of the rings] borns! It can be used for public service. The validation methods are simple. 271 | Some time interval they checks the domain which https://letsencrypt.org/[Let's encrypt] cert generated for with DNS-01 challange (it validates the domain have the key in a TXT record) or HTTP-01 challange where the web server have to serve http:///.well-known/acme-challenge/ . 272 | So its cool. When you have public IP and open port or run in the cloud. If I will have some intention or time I will extend this example with let's encrypt capability. My motivation can be increased with some free beer - but pssst, don't tell it to my wife. 273 | 274 | IMPORTANT: Do not use self-signed certificate for production systems. And it's serious. 275 | 276 | ### docker-compose.yaml 277 | 278 | It is your description of container. I'm not sure that you care how it works. You yust wanna add a new service. You can do it. Yeah. 279 | 280 | #### Add service 281 | 282 | 283 | ``` 284 | whoami: 285 | image: emilevauge/whoami 286 | container_name: ${COMPOSE_PROJECT_NAME}_whoami <1> 287 | restart: unless-stopped <2> 288 | networks: 289 | judo: <3> 290 | aliases: 291 | - whoami.${DOMAIN} <4> 292 | 293 | labels: 294 | - traefik.enable=true <5> 295 | - traefik.backend=whoami <6> 296 | - traefik.docker.network=${COMPOSE_PROJECT_NAME}_judo <7> 297 | 298 | # SSL configuration 299 | - traefik.http.routers.whoami.entryPoints=https <8> 300 | - traefik.http.routers.whoami.rule=host(`whoami.${DOMAIN}`) <9> 301 | - traefik.http.routers.whoami.middlewares=sso@file <10> 302 | - traefik.http.routers.whoami.tls=true <11> 303 | ``` 304 | <1> Container name created from project name + any name. 305 | <2> Run while not stopped. If you make compose in daemon mode, the restart wiill not stop the rock 306 | <3> Network name is JUDO. I know, it is a cheap advertisement, but I'm a as you know a fat old lazy guy. 307 | <4> Alias. Importoant is some container (for example keycloak). Without it the internal name resolution is not okay, 308 | it gives 127.0.0.1 and it will point to wrong service. So in container the domain name have to be resolvable to 309 | docker network address. 310 | <5> Put it to reverse proxy context 311 | <6> Service name is references by the router. 312 | <7> Network is defined for traefik routing. It have to be prfixed with the project name. 313 | <8> It is accessible over https. When trying to access as http, it will replace to https prefix. It is done by 314 | traefik 315 | <9> Host name to listen to. It will be the domain name of host. Here is the place if you wanna make some confusion and making different name as the container name. 316 | <10> The middleware ssl is defined in `config/traefik/dynamic_conf.toml`. It can be edited - on that case its reloaded dynamically, Or 317 | you can translate it to label. I've using that way in my IOT setup. But its a relative little hell. Very long strings, hard to manage, 318 | so config files are better place, but you cannot use nev variable substitution. 319 | <11> It's SSL. We are encoded. Good luck clear text password https://www.youtube.com/watch?v=1p_R7SCKEFU&ab_channel=%C3%96merFarukEngin[miners]! 320 | 321 | When the middleware removed SSO athentication is not required. The Badur's gate is open for everyone. So consider it to secure if there is 322 | not inner security in service or a public site. 323 | 324 | ## Directory layout 325 | 326 | Heh. It sound professional. So again, I'm a lazy fat old fart, so it is for me if there is some logic in the directory structure. 327 | 328 | - config - configuration, environment variables which are referenced from compose. 329 | - cert - the certificates used by containers. I do not recommend to persist certificate in a version control system. It can cause that your 330 | user data can be listed in https://haveibeenpwned.com/[Have I been Pawned?] 331 | - .data - containers persist their state there. Hah. Yeah sometimes there are some states which cannot be forget between restarts. Or you 332 | are the One who setup everything after a start? :) Yes, I know containers. But kubernetes also have PersistentClaims. And some 333 | storage hardware factory have to get some money. Am I right? 334 | And sometimes some side effects have to be hided inside a monad :) Practically it is not part of a version control sytem. Oooo. Everybody 335 | knows github :) You are here. So I'm sure you using one. 336 | 337 | ## Containers 338 | 339 | ### Traefik 340 | 341 | The reverse proxy itelf. It listens on the port 80 and 443. Traefik listens for containers (thats the reason that docker socket have to be 342 | mounted) and when see some marker label on container definition, it will grab that container and making the route rules for it. It's very similar 343 | as OSGi whiteboard pattern works. So you tell me don't know what the OSGi is? You prefer microservices instead of it? Or you hear that 344 | it's a blackmagic technology? Either reason, you can check https://www.youtube.com/watch?v=PYXT5y8gwAg&ab_channel=codecentricAG . One of Netflix 345 | department can operate the 1/10th of microsevice cost with karaf and OSGi. It sound good, right? Maybe the miroservice only just one of the several solutions 346 | and not right for every problem? Okay, okay, you right, I do not know anything. 347 | 348 | #### Compose fargment: 349 | 350 | 351 | ``` 352 | traefik: 353 | image: traefik 354 | restart: unless-stopped 355 | container_name: ${COMPOSE_PROJECT_NAME}_traefik 356 | 357 | ports: 358 | - "0.0.0.0:80:80" <1> 359 | - "0.0.0.0:443:443" <2> 360 | volumes: 361 | - /var/run/docker.sock:/var/run/docker.sock:ro <3> 362 | - ./config/traefik:/etc/traefik <4> 363 | - ./.data/traefik/logs:/logs <5> 364 | - ./cert/_.${DOMAIN}:/etc/cert <6> 365 | 366 | environment: 367 | - TZ=Europe/Budapest <7> 368 | 369 | networks: 370 | judo: 371 | aliases: 372 | - traefik.${DOMAIN} 373 | 374 | labels: 375 | - traefik.enable=true 376 | - traefik.backend=traefik-api 377 | - traefik.docker.network=${COMPOSE_PROJECT_NAME}_judo 378 | - traefik.http.services.traefik.loadbalancer.server.port=8080 <8> 379 | 380 | # SSL configuration 381 | - traefik.http.routers.traefik-ssl.entryPoints=https 382 | - traefik.http.routers.traefik-ssl.rule=host(`traefik.${DOMAIN}`) 383 | - traefik.http.routers.traefik-ssl.middlewares=sso@file 384 | - traefik.http.routers.traefik-ssl.tls=true 385 | 386 | ``` 387 | 388 | <1> http port listens all of available newtork on host machine. It only listens, because if the client haven't got the reflex to use `https` by default, it redirects to https variant of the very same URL. 389 | <2> https port listens all of available network on hist machine. yeah. The dance begins here. I will tell you how it operates. If you change it 390 | I recommend change the URL-s postfixed to that port everywhere. So read this doc, will find it. The reward will be a working system. :) 391 | <3> The socket of docker mounted 392 | <4> Some configuration. It's loaded from file system. If you prefer you can use as label. In my first version I had that. It was not a good idea - 393 | Oh you realy think that I do not make mistakes? If you think that, YOU did make a mistake now - It's importatnt, because in the toml file there is a file reference, and if this volume mount does not exists that path is invalid. 394 | <5> Logs. Oh. In the configuration have to be switched on. It will make logs. I'm not sure its neccessary, because in the container world there is 395 | https://www.elastic.co/what-is/elk-stack[ELK stack], so you dont need to store logs inside text files anymore. But if you like to use grep / awk, than good for you. Do it. 396 | 397 | <7> Timezone. Yes. We are in the center of Europe. But our political system will bring us near to the http://www.balkanfanatik.com/[Balkan Fanatik] soon. Oh yes, yes. I'm too liberal fou our unorthodox system. 398 | 399 | <8> The port traefik dashboard listens on. Yeah. They have some fancy graph about routes. So trafik handles itself as eny other containers. So routing 400 | dashboard! https://traefik.localtest.me. 401 | 402 | The other labels already mentioned in our hellow world example. 403 | 404 | 405 | #### traefik.toml: 406 | 407 | ``` 408 | [log] 409 | level = "DEBUG" <1> 410 | filePath = "/logs/traefik.log" 411 | 412 | [entryPoints] 413 | [entryPoints.http] <2> 414 | address = ":80" 415 | [entryPoints.https] 416 | address = ":443" 417 | 418 | [api] 419 | dashboard = true <3> 420 | insecure = true <4> 421 | 422 | [providers] 423 | [providers.file] <5> 424 | filename = "/etc/traefik/dynamic_conf.toml" 425 | [providers.docker] 426 | endpoint = "unix:///var/run/docker.sock" 427 | watch = true 428 | exposedbydefault = false <6> 429 | defaultrule = "Host(`{{ .Name }}.localtest.me`)" <7> 430 | 431 | [accessLog] 432 | filePath = "/logs/access.log" <8> 433 | 434 | ``` 435 | 436 | <1> Log level. It is DEBUG while configuring, After that point INFO is enough. There is a bunch of message is not for consuming. Just for digging 437 | for errors :) 438 | 439 | <2> The ports mappend as entry point. I know, but the port mapping above is about docker and host machine. Here it tells for trafeik. You know, 440 | like good burocrats everybody have to put ther stamps. 441 | 442 | <3> Dashboard enabled - nice graphs. It draws that very routes which have been set up in the configuration. 443 | 444 | <4> Insecure - Hehaaaa. Its a lie. Insecured by default, but if you already know that everything over the sso@file middleware is protected. Thats so cool that type of Whiteboard extension pattern. Self defense is possible. 445 | 446 | <5> Dynamic conf included here. Dynamic config means when you change content, it will redeploy routes. It's an interesting thing in 447 | container, because some schools teach us the container deployed as it is, and it have to be immutable. When you change it, redeploy it. Yeeah, its a kind of thruth. So if you can do simple rollover over several machines - can create new ones and after stops old ones. - do it right. But here we are talking aboute routing where the route decisions can happen here, and you have maybe only just couple - if you don't have https://www.redhat.com/en/engage/openstack-datasheet-20171008?sc_cid=7013a000002DTTyAAO&gclid=CjwKCAjwr_uCBhAFEiwAX8YJgcJy1WV5BH8V-AX26cj1DSk1SLH7jkaMiy07VMcmIy27pMr0uGHR6BoCa8oQAvD_BwE&gclsrc=aw.ds[OpenStack] like rocket science fueled network resource manager infrastructure. Means you have your own cloud at a large in your yard. So one thing you have to check, tha path have to be mounted as volume if you do not wanna repeat the question - "I've done right, why it is not working?" Same story happendned. Turns out tons of logs and the 3rd line have a little warneng mentioned that for me. So in this case too much log caused problems to identify the real problem. 448 | 449 | <6> We are in control! We are the engineer (ehh, nice world, everybody can be engineer in paper), do not open all of your container by default. 450 | 451 | <7> If you dont give a name for your child, it will give you the name of the container. Yes, I know. You are confused why are typing names in the config for containers. Good question. Just to be control. I am the naming God of my services. Thats all. Some narcistic force in play here. 452 | 453 | <8> Boring log, log and log again. Yes. Don't care. Just mount or delete the entry. 454 | 455 | #### dynamic_conf.toml: 456 | 457 | ``` 458 | [tls.stores] 459 | [tls.stores.default] 460 | [tls.stores.default.defaultCertificate] <1> 461 | certFile = "/etc/cert/cert.pem" 462 | keyFile = "/etc/cert/key.pem" 463 | 464 | [http.routers] 465 | [http.routers.https-only] <2> 466 | entryPoints = ["http"] 467 | middlewares = ["httpsredirect"] 468 | rule = "HostRegexp(`{host:.+}`)" 469 | service = "noop" 470 | 471 | [http.services] 472 | [http.services.noop.loadBalancer] <3> 473 | [[http.services.noop.loadBalancer.servers]] 474 | url = "http://192.168.0.1" 475 | 476 | [http.middlewares] 477 | [http.middlewares.sso.forwardAuth] <4> 478 | address = "http://traefik-fa:4181" <5> 479 | authResponseHeaders = ["X-Forwarded-User", "X-WebAuth-User"] <6> 480 | trustForwardHeader = "true" <7> 481 | [http.middlewares.httpsredirect.redirectScheme] <8> 482 | scheme = "https" 483 | 484 | ``` 485 | 486 | <1> Certificates - Important, because the SSL encoding are made by this service. These certs are the wildcard certificates. When 487 | you wanna type a lot and make different certs for services, you can do it, but on that case have to make sparate routes for that. I'm too 488 | lazy and I'm spending that time with my children instead. 489 | 490 | <2> The HTTP -> HTTPS redirect magic happens here, redirecting to middleware which redirect at <8> 491 | 492 | <3> Its a fallback loadbalancer. Its not required by default. Its only just for as a last chance. 493 | 494 | <4> The Middleware. It decides that the request have to be authenticated or let to go to service. This the middleware which is referenced 495 | as `sso@file` . Do you see the name sso?. After authentication the reposnse message have the token in a cookie. Cookieees! Cookies in boxes. yuppi! 496 | 497 | <5> The other magic is the forward auth container is called inside docker network as a host name. It 498 | have to match with the container service name. 499 | 500 | <6> https://en.wikipedia.org/wiki/X-Forwarded-For[X-Forwarded-User] - is a standardizad way to mark its a proxy request.It helps forwarder proxy 501 | to know what is the target after authentication. You know Post It! helps to organize the hell of request streams. 502 | 503 | <7> As a matter of fact, I don't know exactly how it operates, but keykloak was not able to operate without it. It is enabling to to get these headers from auth forwarder service and accept it. Maybe forward auth creating extra headers which is rerquired? Help me out! It can be checked in te go source codes, but maybe I mentioan already I'm an lazy old fat guy. 504 | 505 | <8> This translates URL schema to https. 506 | 507 | Okay it was long. But Only just think it is long and hard. See the next chapter. It is that makes the real magic. All of stuffs to this point was 508 | easy as pie. The real hack comes after. 509 | 510 | ### traefik-fa: 511 | 512 | Yes. We are here. Center of the universe. Here happens the magic, event horizont reached. 513 | This decides which is authenticated which is not. If it is misconfigured, than maybe you sell 514 | your data for some private soldier in the shadow. 515 | 516 | ``` 517 | traefik-fa: 518 | image: thomseddon/traefik-forward-auth <1> 519 | container_name: ${COMPOSE_PROJECT_NAME}_traefik-fa 520 | restart: unless-stopped 521 | 522 | volumes: 523 | - ./config/traefik/forward.ini:/forward.ini <2> 524 | - ./cert/minica.pem:/etc/ssl/certs/ca-certificates.crt <3> 525 | 526 | environment: 527 | - CONFIG=/forward.ini <4> 528 | 529 | dns_search: ${DOMAIN} <5> 530 | networks: 531 | judo: 532 | aliases: 533 | - auth.${DOMAIN} 534 | 535 | labels: 536 | - traefik.enable=true 537 | - traefik.docker.network=${COMPOSE_PROJECT_NAME}_judo 538 | - traefik.backend=traefik-fa 539 | - traefik.http.services.traefik-fa.loadBalancer.server.port=4181 540 | 541 | # SSL configuration 542 | - traefik.http.routers.traefik-fa-ssl.entryPoints=https 543 | - traefik.http.routers.traefik-fa-ssl.rule=host(`auth.${DOMAIN}`) 544 | - traefik.http.routers.traefik-fa-ssl.middlewares=sso@file 545 | - traefik.http.routers.traefik-fa-ssl.tls=true 546 | 547 | depends_on: <6> 548 | keycloak: 549 | condition: service_healthy 550 | 551 | ``` 552 | 553 | <1> Start with the image. Maybe you are an experienced Load Balancer. You are just tickeling why this 554 | unknown reverse proxy was selected, there is a very cool https://github.com/oauth2-proxy/oauth2-proxy[oauth2-proxy]. It's the abolute star. 555 | Tons of features, out of box support for some alien technologies. BUT. For me it has not worked. I had CSRF issues (later, baby), forums does not help to solve it. Heh, dont know what is is? Its problems with the usage of the certificates on the keys. Yes, it cannot handle well that we get our keys with different pathes, maytbe related that little black magic within traefik about the Proxy header entries. So it was not played nicely with traefik. Maybe there is some hidden things which was not set - yeah, tons of options, so maybe I miss some things. If somebody can do with this 556 | installation with oaut2-proxy, give me. I will test it immediately. So chalange is open :) 557 | 558 | <2> Config files - later. Patience. The time will come soon. 559 | 560 | <3> This settings is mandatory to use very same certification as the trafik uses. The reason is simple. When using local network and domain for that, as I mentioned earlier it casues that the container machines reolves it directly. So it nice if the domain names and the used key is same. OpenID likes that way. If we do in other way, this whole thing became pointless. On that case close this site, delete all keys, use some simple solution, and don't care :). There are some guys in forums crying out allow to skip the domain name mathing check on X.509 keychange. Guys! Think about it! Make some security and immediately avoid it? And after you will show it to your girlfriend what a perfect securoty system you've made? Liar!. Oh, this hole is deep. 561 | <4> Config again. Is this some kind of boomerang. No. Here we says. We mounted the config, time to use. 562 | That the reason of using alias for network name. But ist's not enough. 563 | 564 | <5> Here some short string, which is shows that config is not a bofoon. It just sit there on the silence, and helps to reach the one of most important thing which 565 | allows to work the whole solution. The DNS names not seem too imprtant. But! These lines says to service use internal network aliases to access a service on our given domain. And that's one is an important trick. The 127.0.0.1 (or any other IP address which may not accessible from our docker network) is not resolved from the external domain server (yes, our great localtest.domain is 127.0.0.1), instead of container address is resolved - so no request leaves our safe garden. Heheh, lower risk to temper. It have to be, because our keycloak server is there - instead of your auth proxy outside, but this whole project goal as the long title said is about this integration. And booom! The client URL, cookie URL, and certification URL is matching. There is no difference. And the key is used also is the same - you will know that better after read next entry. Yes. I'm stunned that you are reading. Good to know there is people that have that vocation. Good for you. You are destined to be succesfull :) And don't leave me alone here. 566 | 567 | <6> Healthchek. It's our manager. Care of us. Cares a lot. It helps to orchestrate service start. What is the purpose to start a service when other service not ready to serv our service? So we can put some check there, and the other services uses us, can depends on us. Our service are used just when we are healthy. So we have no COVID-19 or eny other maybe lethal cause, we will not block the whole https://www.imdb.com/title/tt1706620/[train]. 568 | 569 | Other configs are not mentioned. The purpose very same as other services, and I'm trying to be compact, avoiding the unnecessarry word, sentences and paragraphs. 570 | So don't be rude to point out that I'm a liar. 571 | 572 | #### forward.ini: 573 | 574 | ``` 575 | default-provider = oidc <1> 576 | 577 | secret = secret-nonce <2> 578 | 579 | providers.oidc.client-id = oauth-proxy <3> 580 | providers.oidc.client-secret = 72341b6d-7065-4518-a0e4-50ee15025608 <4> 581 | providers.oidc.issuer-url = https://keycloak.localtest.me/auth/realms/master <5> 582 | 583 | log-level = debug <6> 584 | 585 | cookie-domain = localtest.me <6> 586 | auth-host = auth.localtest.me <7> 587 | 588 | whitelist = admin@example.com <8> 589 | 590 | ``` 591 | <1> This tells standard OpenID is used. You can change to google facebok or other auth method. Feel free to do it. 592 | But you have to register an application for that. I hate it. Very time consuming. For facebook I had to make tons of documentation. More time needed for administration, than the technical configuration. Google / Facebook / Apple please. 593 | Is that App development that we are filling a lot of forms? Like a burocrat? Are you https://en.wikipedia.org/wiki/Vogon[vogons]? Really? 594 | Is it the future? And it gives more confifence and security? Screw you! Only I just wanna validate my users by email address, 595 | which is initalized by the user. More sensitive information can be extracted from your advertisment cookies!!! Cookies in the Jar,. That cookies are not fine! 596 | 597 | <2> Client secret - it will help to create https://portswigger.net/web-security/csrf/tokens[CSRF] token. The client is signing the key also. It avoid to stole 598 | the key by a middleware. Don't do any auth in mobile phone without this, because there is some daemons can stole your brand new auth keys ripping off the face of somebody else. A CSRF token is a unique, secret, unpredictable value that is generated by the server-side application and transmitted to the client in such a way that it is included in a subsequent HTTP request made by the client. When the later request is made, the server-side application validates that the request includes the expected token and rejects the request if the token is missing or invalid. CSRF tokens can prevent CSRF attacks by making it impossible for an attacker to construct a fully valid HTTP request suitable for feeding to a victim user. Since the attacker cannot determine or predict the value of a user's CSRF token, they cannot construct a request with all the parameters that are necessary for the application to honor the request. It stores the token in a cookie, so the client will get it and when the next call is coming that cookie contains the required keys and can authenticate 599 | that the client have the required key. 600 | 601 | <3> Client ID used on the IDM - In our case keycloak. So in the keycloak configuration you can find this client! Nice. So bridge is building. Equilibrium is in our door. 602 | 603 | <4> OIDC client secret is to use to validate that the forward server can eat from the IDM server's table. 604 | 605 | <5> Issuer URL. Its important. Thats the URL which is accessed by our forwarder services in back channels. Like in 606 | a movie. The events happen between the service and clients, but some validation is made on that back channel, to be able to valide that the user key is 607 | really okay. Thats the reason why matching of the domain is important. Not only the client, the forwarder server also talks to ID (keycloak) So there is an easter egg. When you change the port of service, you have to change that domain too, and have to 608 | change the keycloak's port too to be able to acces it from internal network same way. So lot of thing . 609 | 610 | <6> This is for browser. Browsers allow valid cookies, don't like some foreigner. It can cause some terrosrist attack. 611 | So for peace it have to be the same domain. And port. Important, when you change port have to change cookie domain too. 612 | 613 | <7> Auth host. This service's host name. because the keycloak after authentication have to fill some header data to be able to get back here. Like in Hansel and Greatel with the crumb. 614 | 615 | <8> Whitelist. Ahh. So if you have valid credentials in keycloak it's not enough. Your name have to be here, so we are not enough confidene. The real security is a complete paranoia. :) But the real reason is we don't beleive google and facebook. 616 | Half of our globe have account in these sites, so I'm not sure that if any of them can access our critical services. Maybe I'm paranoid :) 617 | But the noises tell me nothing to worry. 618 | 619 | And thats all. Ther eare other config options. But from that point it's your call to dig deeper in the rabbit hole. 620 | 621 | ### Keycloak 622 | 623 | So our base of our ceredentials. Maybe thats the reason that it is persisted with postgresql. The configurations 624 | are initaly imported from json. There are some values you can set / change when you change domain or users. 625 | 626 | #### Compose 627 | 628 | ``` 629 | 630 | keycloak: 631 | container_name: ${COMPOSE_PROJECT_NAME}_keycloak 632 | image: quay.io/keycloak/keycloak:12.0.4 633 | restart: unless-stopped 634 | 635 | env_file: 636 | - ./config/keycloak.env <1> 637 | 638 | environment: 639 | - KEYCLOAK_FRONTEND_URL=https://keycloak.${DOMAIN}/auth <2> 640 | - PROXY_ADDRESS_FORWARDING=true <3> 641 | 642 | networks: 643 | judo: 644 | aliases: 645 | - keycloak.${DOMAIN} 646 | 647 | command: 648 | [ 649 | '-b', 650 | '0.0.0.0', <4> 651 | '-Djboss.http.port=80', <5> 652 | '-Djboss.https.port=443', <6> 653 | '-Djboss.socket.binding.port-offset=0', <7> 654 | '-Dkeycloak.migration.action=import', <8> 655 | '-Dkeycloak.migration.provider=dir', 656 | '-Dkeycloak.migration.dir=/realm-config', 657 | '-Dkeycloak.migration.strategy=IGNORE_EXISTING',<9> 658 | ] 659 | 660 | volumes: 661 | - ./cert/_.${DOMAIN}/cert.pem:/etc/x509/https/tls.crt <10> 662 | - ./cert/_.${DOMAIN}/key.pem:/etc/x509/https/tls.key 663 | - ./config/keycloak-realm-config:/realm-config 664 | 665 | labels: 666 | - traefik.enable=true 667 | - traefik.backend=keycloak 668 | - traefik.docker.network=${COMPOSE_PROJECT_NAME}_judo 669 | - traefik.http.services.keycloak.loadBalancer.server.port=80 670 | 671 | # SSL configuration 672 | - traefik.http.routers.keycloak.entryPoints=https 673 | - traefik.http.routers.keycloak.rule=host(`keycloak.${DOMAIN}`) 674 | - traefik.http.routers.keycloak.tls=true 675 | 676 | healthcheck: 677 | test: ["CMD-SHELL", "curl -U --fail http://localhost:80/auth/realms/master"] 678 | interval: 10s 679 | timeout: 1s 680 | retries: 30 681 | 682 | depends_on: 683 | postgres: 684 | condition: service_healthy 685 | 686 | ``` 687 | 688 | <1> Embedd some environment from outside 689 | 690 | <2> Its required for the frontend of keycloak to know where it stands behind the proxy. 691 | 692 | <3> It tells that the <2> defined URL be used. 693 | 694 | <4> Listen in all interfaces inside docker 695 | 696 | <5> HTTP port listens - proxy accessing over HTTP port the keycloak 697 | 698 | <6> HTTPS port open for auth proxy to access. The certificate setting slo mandatory here. 699 | 700 | <7> Offset of all ports. Interesing. When it set EVERY port incremented with this number. So if it is 1000, the HTTPS port become 1443. In docker it cleaner 701 | to keep zero, because keycloak ports will not collide anothger servie's ports. Its for old times sake, when multiple instance of keycloak was executred in same machine. Container can separate, yeah. Good thing. 702 | 703 | <8> Improt the JSON files as initial data. 704 | 705 | <9> To be able to restart the service. Or you can make immutable if does not any persist, only just import. 706 | 707 | <10> The certs again. Oh yeah. Same certs will not coillide another so. Certificartions our passport to the 708 | consitency haeven. 709 | 710 | #### Keycloak.env 711 | 712 | ``` 713 | TZ=Europe/Budapest 714 | DB_VENDOR=POSTGRES 715 | DB_ADDR=postgres 716 | DB_DATABASE=judo 717 | DB_USER=judo 718 | DB_SCHEMA=public 719 | DB_PASSWORD=judo 720 | KEYCLOAK_USER=admin 721 | KEYCLOAK_PASSWORD=judo 722 | PROXY_ADDRESS_FORWARDING=true 723 | 724 | ``` 725 | 726 | Some default. Read it as text. I think no need eny explationion. I'm tired. I think. My energies have to be kept for more important things. 727 | 728 | 729 | #### master-users-0.json: 730 | 731 | The admin@example.com user. Why this json so ugly? Firts of all, it is exported from a running keycloak, and 732 | put here to be imported at start. Second reason, one of example it was configured. I licensed it from another guy (links below). What a nice word - as a matter of fact I stole it. Luckelymy hands will be not cut out for this sin (yet) here. 733 | 734 | #### master-realm.json: 735 | 736 | Thats the configuration where the `oauth-proxy` is living. You win! Another easter egg have been found. On there the redirect uri can match 737 | with the auth-proxy url to be able to call back when authentication happened. So good for you! 738 | 739 | And thats all. postgres is not important here. But I recommend to use it. I've worked lot of RDBMS databases, Postgres is far from the best overall from there. Easy to use, free to use, SQL standard compliant, feature rich. 740 | I know, Oracle can be distributed over continents, but most of the time the database contains several millions of lines in tables maximum, which can be handled with postgresql well. 741 | 742 | ## Some future plans 743 | 744 | In a near future project I will extend this with https://www.elastic.co/what-is/elk-stack[ELK], https://prometheus.io/[Prometheus], https://www.influxdata.com/[InfluxDB] and https://grafana.com/[Grafana] as a complete monitoring setup. Aaaaand you can see boxes!! Color boxes!!! One thing is better than boxes! The color boxes! And sometimes graphs. Did you know that the good graph always shows increasing trends? Hahh. If you don't think you are not a sales person, maybe you are techical guy who thinks the memory usage have to be a flat line. Too much hospital series! That can be the reason that flat line means with dead. What a mess! 745 | 746 | ## Oh errors 747 | 748 | So As you see I'm not perfect and maybe some of my stuffz not as good I think. In that case please teach me. Feel free to put some pull 749 | requests and correct me. I wanna learn! I'm too old, I have not got that sharp brain, so with the extension of knowledge can be 750 | https://www.youtube.com/watch?v=Y4QbJRAWvRU&ab_channel=YelloVEVO[race] with you only. 751 | 752 | ## Source of materials 753 | 754 | 755 | So there is some credit list. Don't you think I created this whole crap? Don't believe I will not share the responsibility and 756 | Will I carry the can alone? And you can dig and learn from it as I did. 757 | 758 | https://community.traefik.io/t/forwardauth-openid-keycloak/1788 759 | 760 | https://www.linkedin.com/pulse/homelab-single-sign-on-tls-aymen-furter/?articleId=6662081833322315776 761 | 762 | https://carey.li/2019/10/01/traefik-2-sso-ssl/ 763 | 764 | https://github.com/Artiume/docker/blob/master/traefik-SSO.yml 765 | 766 | https://github.com/cry/traefik2-demo 767 | 768 | https://github.com/thomseddon/traefik-forward-auth/issues/134 769 | 770 | https://blog.ruanbekker.com/blog/2020/12/23/https-for-local-development-with-minica/ 771 | 772 | https://www.medicalnewstoday.com/articles/types-of-gender-identity#types-of-gender-identity 773 | 774 | https://en.wikipedia.org/wiki/X.509 775 | 776 | https://en.wikipedia.org/wiki/Single_sign-on 777 | 778 | https://letsencrypt.org/ 779 | 780 | https://en.wikipedia.org/wiki/Sed 781 | 782 | https://en.wikipedia.org/wiki/OpenID 783 | 784 | https://portswigger.net/web-security/csrf/tokens 785 | 786 | https://stackoverflow.com/questions/53913032/how-can-i-add-ssl-in-keycloak-in-docker 787 | 788 | https://github.com/coreos/go-oidc/issues/250 789 | 790 | http://docs.docker.oeynet.com/engine/userguide/networking/configure-dns/ 791 | 792 | https://stackoverflow.com/questions/49913355/domain-configuration-in-docker-compose/49925869 793 | 794 | https://stackoverflow.com/questions/45918881/host-resolution-with-docker-and-docker-compose 795 | 796 | https://github.com/thomseddon/traefik-forward-auth/issues/122 797 | 798 | https://en.wikipedia.org/wiki/HTTP_cookie#:~:text=An%20HTTP%20cookie%20(also%20called,browser%20while%20browsing%20a%20website. 799 | 800 | https://itnext.io/a-beginners-guide-to-deploying-a-docker-application-to-production-using-docker-compose-de1feccd2893 801 | 802 | https://gist.github.com/mwidmann/115c2a7059dcce300b61f625d887e5dc 803 | 804 | -------------------------------------------------------------------------------- /build/postgres-hypopg/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:9.6 2 | 3 | RUN apt-get update && apt-get -y install wget build-essential postgresql-server-dev-9.6 4 | 5 | RUN wget https://github.com/dalibo/hypopg/archive/1.0.0.tar.gz && \ 6 | tar xf 1.0.0.tar.gz && \ 7 | cd hypopg-1.0.0 && \ 8 | make && \ 9 | make install 10 | -------------------------------------------------------------------------------- /build/postgres-hypopg/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker build -t postgres-hypo . -------------------------------------------------------------------------------- /cert/.gitignore: -------------------------------------------------------------------------------- 1 | _* 2 | *.pem 3 | *.crt 4 | *.key 5 | 6 | -------------------------------------------------------------------------------- /cert/cert-local.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | #set -euo pipefail 5 | #IFS=$'\n\t' 6 | # 7 | #openssl req -x509 -out ./cert/localhost.crt -keyout ./cert/localhost.key \ 8 | # -newkey rsa:2048 -nodes -sha256 \ 9 | # -subj '/CN=localhost' -extensions EXT -config <( \ 10 | # printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth") 11 | 12 | 13 | # https://blog.ruanbekker.com/blog/2020/12/23/https-for-local-development-with-minica/ 14 | # https://readme.localtest.me/ 15 | 16 | docker run --user $(id -u):$(id -g) -it -v ${PWD}/cert:/output ryantk/minica --domains *.localtest.me -------------------------------------------------------------------------------- /config/keycloak-realm-config/master-realm.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "master", 3 | "realm" : "master", 4 | "displayName" : "Keycloak", 5 | "displayNameHtml" : "
Keycloak
", 6 | "notBefore" : 0, 7 | "revokeRefreshToken" : false, 8 | "refreshTokenMaxReuse" : 0, 9 | "accessTokenLifespan" : 60, 10 | "accessTokenLifespanForImplicitFlow" : 900, 11 | "ssoSessionIdleTimeout" : 1800, 12 | "ssoSessionMaxLifespan" : 36000, 13 | "ssoSessionIdleTimeoutRememberMe" : 0, 14 | "ssoSessionMaxLifespanRememberMe" : 0, 15 | "offlineSessionIdleTimeout" : 2592000, 16 | "offlineSessionMaxLifespanEnabled" : false, 17 | "offlineSessionMaxLifespan" : 5184000, 18 | "clientSessionIdleTimeout" : 0, 19 | "clientSessionMaxLifespan" : 0, 20 | "accessCodeLifespan" : 60, 21 | "accessCodeLifespanUserAction" : 300, 22 | "accessCodeLifespanLogin" : 1800, 23 | "actionTokenGeneratedByAdminLifespan" : 43200, 24 | "actionTokenGeneratedByUserLifespan" : 300, 25 | "enabled" : true, 26 | "sslRequired" : "external", 27 | "registrationAllowed" : false, 28 | "registrationEmailAsUsername" : false, 29 | "rememberMe" : false, 30 | "verifyEmail" : false, 31 | "loginWithEmailAllowed" : true, 32 | "duplicateEmailsAllowed" : false, 33 | "resetPasswordAllowed" : false, 34 | "editUsernameAllowed" : false, 35 | "bruteForceProtected" : false, 36 | "permanentLockout" : false, 37 | "maxFailureWaitSeconds" : 900, 38 | "minimumQuickLoginWaitSeconds" : 60, 39 | "waitIncrementSeconds" : 60, 40 | "quickLoginCheckMilliSeconds" : 1000, 41 | "maxDeltaTimeSeconds" : 43200, 42 | "failureFactor" : 30, 43 | "roles" : { 44 | "realm" : [ { 45 | "id" : "32626c92-4327-40f1-b318-76a6b5c7eee5", 46 | "name" : "offline_access", 47 | "description" : "${role_offline-access}", 48 | "composite" : false, 49 | "clientRole" : false, 50 | "containerId" : "master", 51 | "attributes" : { } 52 | }, { 53 | "id" : "e36da570-7ae0-4323-8b39-73eb92ce722f", 54 | "name" : "admin", 55 | "description" : "${role_admin}", 56 | "composite" : true, 57 | "composites" : { 58 | "realm" : [ "create-realm" ], 59 | "client" : { 60 | "master-realm" : [ "query-groups", "create-client", "query-realms", "view-authorization", "view-realm", "manage-clients", "query-users", "manage-realm", "view-events", "manage-events", "view-identity-providers", "view-users", "manage-identity-providers", "manage-authorization", "manage-users", "view-clients", "query-clients", "impersonation" ] 61 | } 62 | }, 63 | "clientRole" : false, 64 | "containerId" : "master", 65 | "attributes" : { } 66 | }, { 67 | "id" : "71aca46c-6fcf-4456-ba87-6374e70108a2", 68 | "name" : "uma_authorization", 69 | "description" : "${role_uma_authorization}", 70 | "composite" : false, 71 | "clientRole" : false, 72 | "containerId" : "master", 73 | "attributes" : { } 74 | }, { 75 | "id" : "6ca3fee8-1a3f-4068-a311-6e81223a884b", 76 | "name" : "create-realm", 77 | "description" : "${role_create-realm}", 78 | "composite" : false, 79 | "clientRole" : false, 80 | "containerId" : "master", 81 | "attributes" : { } 82 | } ], 83 | "client" : { 84 | "oauth-proxy" : [ ], 85 | "security-admin-console" : [ ], 86 | "admin-cli" : [ ], 87 | "account-console" : [ ], 88 | "broker" : [ { 89 | "id" : "2cc5e40c-0a28-4c09-85eb-20cd47ac1351", 90 | "name" : "read-token", 91 | "description" : "${role_read-token}", 92 | "composite" : false, 93 | "clientRole" : true, 94 | "containerId" : "380985f1-61c7-4940-93ae-7a09458071ca", 95 | "attributes" : { } 96 | } ], 97 | "master-realm" : [ { 98 | "id" : "a8271c2c-6437-4ca5-ae83-49ea5fe1318d", 99 | "name" : "query-groups", 100 | "description" : "${role_query-groups}", 101 | "composite" : false, 102 | "clientRole" : true, 103 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 104 | "attributes" : { } 105 | }, { 106 | "id" : "5a7cb1ae-7dac-486b-bf7b-4d7fbc5adb31", 107 | "name" : "create-client", 108 | "description" : "${role_create-client}", 109 | "composite" : false, 110 | "clientRole" : true, 111 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 112 | "attributes" : { } 113 | }, { 114 | "id" : "a9e6a2fa-c31b-4959-bf8a-a46fcc9c65ec", 115 | "name" : "view-authorization", 116 | "description" : "${role_view-authorization}", 117 | "composite" : false, 118 | "clientRole" : true, 119 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 120 | "attributes" : { } 121 | }, { 122 | "id" : "1cef34e3-569a-4d2b-ba5c-aafe5c7ab423", 123 | "name" : "query-realms", 124 | "description" : "${role_query-realms}", 125 | "composite" : false, 126 | "clientRole" : true, 127 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 128 | "attributes" : { } 129 | }, { 130 | "id" : "efc46075-30cd-4600-aa92-2ae4a171d0c2", 131 | "name" : "view-realm", 132 | "description" : "${role_view-realm}", 133 | "composite" : false, 134 | "clientRole" : true, 135 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 136 | "attributes" : { } 137 | }, { 138 | "id" : "9ffacaf0-afc6-49e9-8708-ef35ac40f3f8", 139 | "name" : "manage-clients", 140 | "description" : "${role_manage-clients}", 141 | "composite" : false, 142 | "clientRole" : true, 143 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 144 | "attributes" : { } 145 | }, { 146 | "id" : "90662091-b3bc-4ae4-83c9-a4f53e7e9eeb", 147 | "name" : "query-users", 148 | "description" : "${role_query-users}", 149 | "composite" : false, 150 | "clientRole" : true, 151 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 152 | "attributes" : { } 153 | }, { 154 | "id" : "9a5fbc9d-6fae-4155-86f6-72fd399aa126", 155 | "name" : "manage-realm", 156 | "description" : "${role_manage-realm}", 157 | "composite" : false, 158 | "clientRole" : true, 159 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 160 | "attributes" : { } 161 | }, { 162 | "id" : "03f46127-9436-477d-8c7f-58569f45237c", 163 | "name" : "view-events", 164 | "description" : "${role_view-events}", 165 | "composite" : false, 166 | "clientRole" : true, 167 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 168 | "attributes" : { } 169 | }, { 170 | "id" : "f10eaea2-90ab-4310-9d5f-8d986564d061", 171 | "name" : "view-identity-providers", 172 | "description" : "${role_view-identity-providers}", 173 | "composite" : false, 174 | "clientRole" : true, 175 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 176 | "attributes" : { } 177 | }, { 178 | "id" : "2403e038-2cf7-4b06-b5cb-33a417a00d8d", 179 | "name" : "manage-events", 180 | "description" : "${role_manage-events}", 181 | "composite" : false, 182 | "clientRole" : true, 183 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 184 | "attributes" : { } 185 | }, { 186 | "id" : "677d057b-66f8-4163-9948-95fdbd06dfdc", 187 | "name" : "view-users", 188 | "description" : "${role_view-users}", 189 | "composite" : true, 190 | "composites" : { 191 | "client" : { 192 | "master-realm" : [ "query-groups", "query-users" ] 193 | } 194 | }, 195 | "clientRole" : true, 196 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 197 | "attributes" : { } 198 | }, { 199 | "id" : "dc140fa6-bf2c-49f2-b8c9-fc34ef8a2c63", 200 | "name" : "manage-identity-providers", 201 | "description" : "${role_manage-identity-providers}", 202 | "composite" : false, 203 | "clientRole" : true, 204 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 205 | "attributes" : { } 206 | }, { 207 | "id" : "155bf234-4895-4855-95c2-a460518f57e8", 208 | "name" : "manage-authorization", 209 | "description" : "${role_manage-authorization}", 210 | "composite" : false, 211 | "clientRole" : true, 212 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 213 | "attributes" : { } 214 | }, { 215 | "id" : "5441ec71-3eac-4696-9e68-0de54fbdde98", 216 | "name" : "manage-users", 217 | "description" : "${role_manage-users}", 218 | "composite" : false, 219 | "clientRole" : true, 220 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 221 | "attributes" : { } 222 | }, { 223 | "id" : "2db0f052-cb91-4170-81fd-107756b162f7", 224 | "name" : "view-clients", 225 | "description" : "${role_view-clients}", 226 | "composite" : true, 227 | "composites" : { 228 | "client" : { 229 | "master-realm" : [ "query-clients" ] 230 | } 231 | }, 232 | "clientRole" : true, 233 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 234 | "attributes" : { } 235 | }, { 236 | "id" : "e1d7f235-8bf2-40b8-be49-49aca70a5088", 237 | "name" : "query-clients", 238 | "description" : "${role_query-clients}", 239 | "composite" : false, 240 | "clientRole" : true, 241 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 242 | "attributes" : { } 243 | }, { 244 | "id" : "e743f66a-2f56-4b97-b34b-33f06ff1e739", 245 | "name" : "impersonation", 246 | "description" : "${role_impersonation}", 247 | "composite" : false, 248 | "clientRole" : true, 249 | "containerId" : "7174c175-1887-4e57-b95b-969fe040deff", 250 | "attributes" : { } 251 | } ], 252 | "account" : [ { 253 | "id" : "64d8f532-839e-4386-b2eb-fe8848b0a9de", 254 | "name" : "manage-consent", 255 | "description" : "${role_manage-consent}", 256 | "composite" : true, 257 | "composites" : { 258 | "client" : { 259 | "account" : [ "view-consent" ] 260 | } 261 | }, 262 | "clientRole" : true, 263 | "containerId" : "a367038f-fe01-4459-9f91-7ad0cf498533", 264 | "attributes" : { } 265 | }, { 266 | "id" : "3ec22748-960f-4f96-a43e-50f54a02dc23", 267 | "name" : "view-profile", 268 | "description" : "${role_view-profile}", 269 | "composite" : false, 270 | "clientRole" : true, 271 | "containerId" : "a367038f-fe01-4459-9f91-7ad0cf498533", 272 | "attributes" : { } 273 | }, { 274 | "id" : "177d18e4-46b0-4ea3-8b70-327486ce5bb2", 275 | "name" : "view-applications", 276 | "description" : "${role_view-applications}", 277 | "composite" : false, 278 | "clientRole" : true, 279 | "containerId" : "a367038f-fe01-4459-9f91-7ad0cf498533", 280 | "attributes" : { } 281 | }, { 282 | "id" : "703643d6-0542-4e27-9737-7c442925c18c", 283 | "name" : "manage-account-links", 284 | "description" : "${role_manage-account-links}", 285 | "composite" : false, 286 | "clientRole" : true, 287 | "containerId" : "a367038f-fe01-4459-9f91-7ad0cf498533", 288 | "attributes" : { } 289 | }, { 290 | "id" : "c64f9f66-d762-4337-8833-cf31c316e8a7", 291 | "name" : "view-consent", 292 | "description" : "${role_view-consent}", 293 | "composite" : false, 294 | "clientRole" : true, 295 | "containerId" : "a367038f-fe01-4459-9f91-7ad0cf498533", 296 | "attributes" : { } 297 | }, { 298 | "id" : "611f568b-0fdd-4d2e-ba34-03136cd486c4", 299 | "name" : "manage-account", 300 | "description" : "${role_manage-account}", 301 | "composite" : true, 302 | "composites" : { 303 | "client" : { 304 | "account" : [ "manage-account-links" ] 305 | } 306 | }, 307 | "clientRole" : true, 308 | "containerId" : "a367038f-fe01-4459-9f91-7ad0cf498533", 309 | "attributes" : { } 310 | } ] 311 | } 312 | }, 313 | "groups" : [ ], 314 | "defaultRoles" : [ "offline_access", "uma_authorization" ], 315 | "requiredCredentials" : [ "password" ], 316 | "otpPolicyType" : "totp", 317 | "otpPolicyAlgorithm" : "HmacSHA1", 318 | "otpPolicyInitialCounter" : 0, 319 | "otpPolicyDigits" : 6, 320 | "otpPolicyLookAheadWindow" : 1, 321 | "otpPolicyPeriod" : 30, 322 | "otpSupportedApplications" : [ "FreeOTP", "Google Authenticator" ], 323 | "webAuthnPolicyRpEntityName" : "keycloak", 324 | "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], 325 | "webAuthnPolicyRpId" : "", 326 | "webAuthnPolicyAttestationConveyancePreference" : "not specified", 327 | "webAuthnPolicyAuthenticatorAttachment" : "not specified", 328 | "webAuthnPolicyRequireResidentKey" : "not specified", 329 | "webAuthnPolicyUserVerificationRequirement" : "not specified", 330 | "webAuthnPolicyCreateTimeout" : 0, 331 | "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, 332 | "webAuthnPolicyAcceptableAaguids" : [ ], 333 | "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", 334 | "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], 335 | "webAuthnPolicyPasswordlessRpId" : "", 336 | "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", 337 | "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", 338 | "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", 339 | "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", 340 | "webAuthnPolicyPasswordlessCreateTimeout" : 0, 341 | "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, 342 | "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], 343 | "scopeMappings" : [ { 344 | "clientScope" : "offline_access", 345 | "roles" : [ "offline_access" ] 346 | } ], 347 | "clientScopeMappings" : { 348 | "account" : [ { 349 | "client" : "account-console", 350 | "roles" : [ "manage-account" ] 351 | } ] 352 | }, 353 | "clients" : [ { 354 | "id" : "a367038f-fe01-4459-9f91-7ad0cf498533", 355 | "clientId" : "account", 356 | "name" : "${client_account}", 357 | "rootUrl" : "${authBaseUrl}", 358 | "baseUrl" : "/realms/master/account/", 359 | "surrogateAuthRequired" : false, 360 | "enabled" : true, 361 | "alwaysDisplayInConsole" : false, 362 | "clientAuthenticatorType" : "client-secret", 363 | "secret" : "0896a464-da81-4454-bee9-b56bdbad9e7f", 364 | "defaultRoles" : [ "view-profile", "manage-account" ], 365 | "redirectUris" : [ "/realms/master/account/*" ], 366 | "webOrigins" : [ ], 367 | "notBefore" : 0, 368 | "bearerOnly" : false, 369 | "consentRequired" : false, 370 | "standardFlowEnabled" : true, 371 | "implicitFlowEnabled" : false, 372 | "directAccessGrantsEnabled" : false, 373 | "serviceAccountsEnabled" : false, 374 | "publicClient" : false, 375 | "frontchannelLogout" : false, 376 | "protocol" : "openid-connect", 377 | "attributes" : { }, 378 | "authenticationFlowBindingOverrides" : { }, 379 | "fullScopeAllowed" : false, 380 | "nodeReRegistrationTimeout" : 0, 381 | "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], 382 | "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] 383 | }, { 384 | "id" : "72f75604-1e21-407c-b967-790aafd11534", 385 | "clientId" : "account-console", 386 | "name" : "${client_account-console}", 387 | "rootUrl" : "${authBaseUrl}", 388 | "baseUrl" : "/realms/master/account/", 389 | "surrogateAuthRequired" : false, 390 | "enabled" : true, 391 | "alwaysDisplayInConsole" : false, 392 | "clientAuthenticatorType" : "client-secret", 393 | "secret" : "91f85142-ee18-4e30-9949-e5acb701bdee", 394 | "redirectUris" : [ "/realms/master/account/*" ], 395 | "webOrigins" : [ ], 396 | "notBefore" : 0, 397 | "bearerOnly" : false, 398 | "consentRequired" : false, 399 | "standardFlowEnabled" : true, 400 | "implicitFlowEnabled" : false, 401 | "directAccessGrantsEnabled" : false, 402 | "serviceAccountsEnabled" : false, 403 | "publicClient" : true, 404 | "frontchannelLogout" : false, 405 | "protocol" : "openid-connect", 406 | "attributes" : { 407 | "pkce.code.challenge.method" : "S256" 408 | }, 409 | "authenticationFlowBindingOverrides" : { }, 410 | "fullScopeAllowed" : false, 411 | "nodeReRegistrationTimeout" : 0, 412 | "protocolMappers" : [ { 413 | "id" : "2772c101-0dba-49b7-9627-5aaddc666939", 414 | "name" : "audience resolve", 415 | "protocol" : "openid-connect", 416 | "protocolMapper" : "oidc-audience-resolve-mapper", 417 | "consentRequired" : false, 418 | "config" : { } 419 | } ], 420 | "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], 421 | "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] 422 | }, { 423 | "id" : "b13fd0de-3be0-4a08-bc5d-d1de34421b1a", 424 | "clientId" : "admin-cli", 425 | "name" : "${client_admin-cli}", 426 | "surrogateAuthRequired" : false, 427 | "enabled" : true, 428 | "alwaysDisplayInConsole" : false, 429 | "clientAuthenticatorType" : "client-secret", 430 | "secret" : "4640af2e-b4a6-44eb-85ec-6278a62a4f01", 431 | "redirectUris" : [ ], 432 | "webOrigins" : [ ], 433 | "notBefore" : 0, 434 | "bearerOnly" : false, 435 | "consentRequired" : false, 436 | "standardFlowEnabled" : false, 437 | "implicitFlowEnabled" : false, 438 | "directAccessGrantsEnabled" : true, 439 | "serviceAccountsEnabled" : false, 440 | "publicClient" : true, 441 | "frontchannelLogout" : false, 442 | "protocol" : "openid-connect", 443 | "attributes" : { }, 444 | "authenticationFlowBindingOverrides" : { }, 445 | "fullScopeAllowed" : false, 446 | "nodeReRegistrationTimeout" : 0, 447 | "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], 448 | "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] 449 | }, { 450 | "id" : "380985f1-61c7-4940-93ae-7a09458071ca", 451 | "clientId" : "broker", 452 | "name" : "${client_broker}", 453 | "surrogateAuthRequired" : false, 454 | "enabled" : true, 455 | "alwaysDisplayInConsole" : false, 456 | "clientAuthenticatorType" : "client-secret", 457 | "secret" : "65d2ba2b-bcae-49ff-9f56-77c818f55930", 458 | "redirectUris" : [ ], 459 | "webOrigins" : [ ], 460 | "notBefore" : 0, 461 | "bearerOnly" : false, 462 | "consentRequired" : false, 463 | "standardFlowEnabled" : true, 464 | "implicitFlowEnabled" : false, 465 | "directAccessGrantsEnabled" : false, 466 | "serviceAccountsEnabled" : false, 467 | "publicClient" : false, 468 | "frontchannelLogout" : false, 469 | "protocol" : "openid-connect", 470 | "attributes" : { }, 471 | "authenticationFlowBindingOverrides" : { }, 472 | "fullScopeAllowed" : false, 473 | "nodeReRegistrationTimeout" : 0, 474 | "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], 475 | "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] 476 | }, { 477 | "id" : "7174c175-1887-4e57-b95b-969fe040deff", 478 | "clientId" : "master-realm", 479 | "name" : "master Realm", 480 | "surrogateAuthRequired" : false, 481 | "enabled" : true, 482 | "alwaysDisplayInConsole" : false, 483 | "clientAuthenticatorType" : "client-secret", 484 | "secret" : "40f73851-a94c-4091-90de-aeee8ca1acf8", 485 | "redirectUris" : [ ], 486 | "webOrigins" : [ ], 487 | "notBefore" : 0, 488 | "bearerOnly" : true, 489 | "consentRequired" : false, 490 | "standardFlowEnabled" : true, 491 | "implicitFlowEnabled" : false, 492 | "directAccessGrantsEnabled" : false, 493 | "serviceAccountsEnabled" : false, 494 | "publicClient" : false, 495 | "frontchannelLogout" : false, 496 | "attributes" : { }, 497 | "authenticationFlowBindingOverrides" : { }, 498 | "fullScopeAllowed" : true, 499 | "nodeReRegistrationTimeout" : 0, 500 | "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], 501 | "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] 502 | }, 503 | { 504 | "id": "0493c7c6-6e20-49ea-9acb-627c0b52d400", 505 | "clientId": "oauth-proxy", 506 | "surrogateAuthRequired": false, 507 | "enabled": true, 508 | "alwaysDisplayInConsole": false, 509 | "clientAuthenticatorType": "client-secret", 510 | "secret": "72341b6d-7065-4518-a0e4-50ee15025608", 511 | "redirectUris": [ 512 | "https://auth.localtest.me/_oauth" 513 | ], 514 | "webOrigins": [], 515 | "notBefore": 0, 516 | "bearerOnly": false, 517 | "consentRequired": false, 518 | "standardFlowEnabled": true, 519 | "implicitFlowEnabled": false, 520 | "directAccessGrantsEnabled": true, 521 | "serviceAccountsEnabled": false, 522 | "publicClient": false, 523 | "frontchannelLogout": false, 524 | "protocol": "openid-connect", 525 | "attributes": { 526 | "saml.assertion.signature": "false", 527 | "saml.force.post.binding": "false", 528 | "saml.multivalued.roles": "false", 529 | "saml.encrypt": "false", 530 | "saml.server.signature": "false", 531 | "saml.server.signature.keyinfo.ext": "false", 532 | "exclude.session.state.from.auth.response": "false", 533 | "saml_force_name_id_format": "false", 534 | "saml.client.signature": "false", 535 | "tls.client.certificate.bound.access.tokens": "false", 536 | "saml.authnstatement": "false", 537 | "display.on.consent.screen": "false", 538 | "saml.onetimeuse.condition": "false" 539 | }, 540 | "authenticationFlowBindingOverrides": {}, 541 | "fullScopeAllowed": true, 542 | "nodeReRegistrationTimeout": -1, 543 | "defaultClientScopes": [ 544 | "web-origins", 545 | "role_list", 546 | "roles", 547 | "profile", 548 | "email" 549 | ], 550 | "optionalClientScopes": [ 551 | "address", 552 | "phone", 553 | "offline_access", 554 | "microprofile-jwt" 555 | ] 556 | }, { 557 | "id" : "2a3ad1fd-a30d-4b72-89c4-bed12f178338", 558 | "clientId" : "security-admin-console", 559 | "name" : "${client_security-admin-console}", 560 | "rootUrl" : "${authAdminUrl}", 561 | "baseUrl" : "/admin/master/console/", 562 | "surrogateAuthRequired" : false, 563 | "enabled" : true, 564 | "alwaysDisplayInConsole" : false, 565 | "clientAuthenticatorType" : "client-secret", 566 | "secret" : "b234b7aa-8417-410f-b3fd-c57434d3aa4a", 567 | "redirectUris" : [ "/admin/master/console/*" ], 568 | "webOrigins" : [ "+" ], 569 | "notBefore" : 0, 570 | "bearerOnly" : false, 571 | "consentRequired" : false, 572 | "standardFlowEnabled" : true, 573 | "implicitFlowEnabled" : false, 574 | "directAccessGrantsEnabled" : false, 575 | "serviceAccountsEnabled" : false, 576 | "publicClient" : true, 577 | "frontchannelLogout" : false, 578 | "protocol" : "openid-connect", 579 | "attributes" : { 580 | "pkce.code.challenge.method" : "S256" 581 | }, 582 | "authenticationFlowBindingOverrides" : { }, 583 | "fullScopeAllowed" : false, 584 | "nodeReRegistrationTimeout" : 0, 585 | "protocolMappers" : [ { 586 | "id" : "5885b0d3-a917-4b52-8380-f37d0754a2ef", 587 | "name" : "locale", 588 | "protocol" : "openid-connect", 589 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 590 | "consentRequired" : false, 591 | "config" : { 592 | "userinfo.token.claim" : "true", 593 | "user.attribute" : "locale", 594 | "id.token.claim" : "true", 595 | "access.token.claim" : "true", 596 | "claim.name" : "locale", 597 | "jsonType.label" : "String" 598 | } 599 | } ], 600 | "defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ], 601 | "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] 602 | } ], 603 | "clientScopes" : [ { 604 | "id" : "47ea3b67-4f0c-4c7e-8ac6-a33a3d655894", 605 | "name" : "address", 606 | "description" : "OpenID Connect built-in scope: address", 607 | "protocol" : "openid-connect", 608 | "attributes" : { 609 | "include.in.token.scope" : "true", 610 | "display.on.consent.screen" : "true", 611 | "consent.screen.text" : "${addressScopeConsentText}" 612 | }, 613 | "protocolMappers" : [ { 614 | "id" : "4be0ca19-0ec7-4cc1-b263-845ea539ff12", 615 | "name" : "address", 616 | "protocol" : "openid-connect", 617 | "protocolMapper" : "oidc-address-mapper", 618 | "consentRequired" : false, 619 | "config" : { 620 | "user.attribute.formatted" : "formatted", 621 | "user.attribute.country" : "country", 622 | "user.attribute.postal_code" : "postal_code", 623 | "userinfo.token.claim" : "true", 624 | "user.attribute.street" : "street", 625 | "id.token.claim" : "true", 626 | "user.attribute.region" : "region", 627 | "access.token.claim" : "true", 628 | "user.attribute.locality" : "locality" 629 | } 630 | } ] 631 | }, { 632 | "id" : "aba72e57-540f-4825-95b7-2d143be028cc", 633 | "name" : "email", 634 | "description" : "OpenID Connect built-in scope: email", 635 | "protocol" : "openid-connect", 636 | "attributes" : { 637 | "include.in.token.scope" : "true", 638 | "display.on.consent.screen" : "true", 639 | "consent.screen.text" : "${emailScopeConsentText}" 640 | }, 641 | "protocolMappers" : [ { 642 | "id" : "7fe82724-5748-4b6d-9708-a028f5d3b970", 643 | "name" : "email verified", 644 | "protocol" : "openid-connect", 645 | "protocolMapper" : "oidc-usermodel-property-mapper", 646 | "consentRequired" : false, 647 | "config" : { 648 | "userinfo.token.claim" : "true", 649 | "user.attribute" : "emailVerified", 650 | "id.token.claim" : "true", 651 | "access.token.claim" : "true", 652 | "claim.name" : "email_verified", 653 | "jsonType.label" : "boolean" 654 | } 655 | }, { 656 | "id" : "e42f334e-cfae-44a0-905d-c3ef215feaae", 657 | "name" : "email", 658 | "protocol" : "openid-connect", 659 | "protocolMapper" : "oidc-usermodel-property-mapper", 660 | "consentRequired" : false, 661 | "config" : { 662 | "userinfo.token.claim" : "true", 663 | "user.attribute" : "email", 664 | "id.token.claim" : "true", 665 | "access.token.claim" : "true", 666 | "claim.name" : "email", 667 | "jsonType.label" : "String" 668 | } 669 | } ] 670 | }, { 671 | "id" : "ec765598-bd71-4318-86c3-b3f81a41c99e", 672 | "name" : "microprofile-jwt", 673 | "description" : "Microprofile - JWT built-in scope", 674 | "protocol" : "openid-connect", 675 | "attributes" : { 676 | "include.in.token.scope" : "true", 677 | "display.on.consent.screen" : "false" 678 | }, 679 | "protocolMappers" : [ { 680 | "id" : "90694036-4014-4672-a2c8-c68319e9308a", 681 | "name" : "upn", 682 | "protocol" : "openid-connect", 683 | "protocolMapper" : "oidc-usermodel-property-mapper", 684 | "consentRequired" : false, 685 | "config" : { 686 | "userinfo.token.claim" : "true", 687 | "user.attribute" : "username", 688 | "id.token.claim" : "true", 689 | "access.token.claim" : "true", 690 | "claim.name" : "upn", 691 | "jsonType.label" : "String" 692 | } 693 | }, { 694 | "id" : "f7b0fcc0-6139-4158-ac45-34fd9a58a5ef", 695 | "name" : "groups", 696 | "protocol" : "openid-connect", 697 | "protocolMapper" : "oidc-usermodel-realm-role-mapper", 698 | "consentRequired" : false, 699 | "config" : { 700 | "multivalued" : "true", 701 | "user.attribute" : "foo", 702 | "id.token.claim" : "true", 703 | "access.token.claim" : "true", 704 | "claim.name" : "groups", 705 | "jsonType.label" : "String" 706 | } 707 | } ] 708 | }, { 709 | "id" : "8a09267b-3634-4a9c-baab-6f2fb4137347", 710 | "name" : "offline_access", 711 | "description" : "OpenID Connect built-in scope: offline_access", 712 | "protocol" : "openid-connect", 713 | "attributes" : { 714 | "consent.screen.text" : "${offlineAccessScopeConsentText}", 715 | "display.on.consent.screen" : "true" 716 | } 717 | }, { 718 | "id" : "3a48c5dd-33a8-4be0-9d2e-30fd7f98363a", 719 | "name" : "phone", 720 | "description" : "OpenID Connect built-in scope: phone", 721 | "protocol" : "openid-connect", 722 | "attributes" : { 723 | "include.in.token.scope" : "true", 724 | "display.on.consent.screen" : "true", 725 | "consent.screen.text" : "${phoneScopeConsentText}" 726 | }, 727 | "protocolMappers" : [ { 728 | "id" : "5427d1b4-ba79-412a-b23c-da640a98980c", 729 | "name" : "phone number", 730 | "protocol" : "openid-connect", 731 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 732 | "consentRequired" : false, 733 | "config" : { 734 | "userinfo.token.claim" : "true", 735 | "user.attribute" : "phoneNumber", 736 | "id.token.claim" : "true", 737 | "access.token.claim" : "true", 738 | "claim.name" : "phone_number", 739 | "jsonType.label" : "String" 740 | } 741 | }, { 742 | "id" : "31d4a53f-6503-40e8-bd9d-79a7c46c4fbe", 743 | "name" : "phone number verified", 744 | "protocol" : "openid-connect", 745 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 746 | "consentRequired" : false, 747 | "config" : { 748 | "userinfo.token.claim" : "true", 749 | "user.attribute" : "phoneNumberVerified", 750 | "id.token.claim" : "true", 751 | "access.token.claim" : "true", 752 | "claim.name" : "phone_number_verified", 753 | "jsonType.label" : "boolean" 754 | } 755 | } ] 756 | }, { 757 | "id" : "5921a9e9-7fec-4471-95e3-dd96eebdec58", 758 | "name" : "profile", 759 | "description" : "OpenID Connect built-in scope: profile", 760 | "protocol" : "openid-connect", 761 | "attributes" : { 762 | "include.in.token.scope" : "true", 763 | "display.on.consent.screen" : "true", 764 | "consent.screen.text" : "${profileScopeConsentText}" 765 | }, 766 | "protocolMappers" : [ { 767 | "id" : "4fa92092-ee0d-4dc7-a63b-1e3b02d35ebb", 768 | "name" : "zoneinfo", 769 | "protocol" : "openid-connect", 770 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 771 | "consentRequired" : false, 772 | "config" : { 773 | "userinfo.token.claim" : "true", 774 | "user.attribute" : "zoneinfo", 775 | "id.token.claim" : "true", 776 | "access.token.claim" : "true", 777 | "claim.name" : "zoneinfo", 778 | "jsonType.label" : "String" 779 | } 780 | }, { 781 | "id" : "1a5cc2e2-c983-4150-8583-23a7f5c826bf", 782 | "name" : "family name", 783 | "protocol" : "openid-connect", 784 | "protocolMapper" : "oidc-usermodel-property-mapper", 785 | "consentRequired" : false, 786 | "config" : { 787 | "userinfo.token.claim" : "true", 788 | "user.attribute" : "lastName", 789 | "id.token.claim" : "true", 790 | "access.token.claim" : "true", 791 | "claim.name" : "family_name", 792 | "jsonType.label" : "String" 793 | } 794 | }, { 795 | "id" : "67931f77-722a-492d-b581-a953e26b7d44", 796 | "name" : "full name", 797 | "protocol" : "openid-connect", 798 | "protocolMapper" : "oidc-full-name-mapper", 799 | "consentRequired" : false, 800 | "config" : { 801 | "id.token.claim" : "true", 802 | "access.token.claim" : "true", 803 | "userinfo.token.claim" : "true" 804 | } 805 | }, { 806 | "id" : "10f6ac36-3a63-4e1c-ac69-c095588f5967", 807 | "name" : "locale", 808 | "protocol" : "openid-connect", 809 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 810 | "consentRequired" : false, 811 | "config" : { 812 | "userinfo.token.claim" : "true", 813 | "user.attribute" : "locale", 814 | "id.token.claim" : "true", 815 | "access.token.claim" : "true", 816 | "claim.name" : "locale", 817 | "jsonType.label" : "String" 818 | } 819 | }, { 820 | "id" : "205d9dce-b6c8-4b1d-9c9c-fa24788651cf", 821 | "name" : "picture", 822 | "protocol" : "openid-connect", 823 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 824 | "consentRequired" : false, 825 | "config" : { 826 | "userinfo.token.claim" : "true", 827 | "user.attribute" : "picture", 828 | "id.token.claim" : "true", 829 | "access.token.claim" : "true", 830 | "claim.name" : "picture", 831 | "jsonType.label" : "String" 832 | } 833 | }, { 834 | "id" : "638216c8-ea8c-40e3-9429-771e9278920e", 835 | "name" : "gender", 836 | "protocol" : "openid-connect", 837 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 838 | "consentRequired" : false, 839 | "config" : { 840 | "userinfo.token.claim" : "true", 841 | "user.attribute" : "gender", 842 | "id.token.claim" : "true", 843 | "access.token.claim" : "true", 844 | "claim.name" : "gender", 845 | "jsonType.label" : "String" 846 | } 847 | }, { 848 | "id" : "39c17eae-8ea7-422c-ae21-b8876bf12184", 849 | "name" : "birthdate", 850 | "protocol" : "openid-connect", 851 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 852 | "consentRequired" : false, 853 | "config" : { 854 | "userinfo.token.claim" : "true", 855 | "user.attribute" : "birthdate", 856 | "id.token.claim" : "true", 857 | "access.token.claim" : "true", 858 | "claim.name" : "birthdate", 859 | "jsonType.label" : "String" 860 | } 861 | }, { 862 | "id" : "01c559cf-94f2-46ad-b965-3b2e1db1a2a6", 863 | "name" : "updated at", 864 | "protocol" : "openid-connect", 865 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 866 | "consentRequired" : false, 867 | "config" : { 868 | "userinfo.token.claim" : "true", 869 | "user.attribute" : "updatedAt", 870 | "id.token.claim" : "true", 871 | "access.token.claim" : "true", 872 | "claim.name" : "updated_at", 873 | "jsonType.label" : "String" 874 | } 875 | }, { 876 | "id" : "1693b5ab-28eb-485d-835d-2ae070ccb3ba", 877 | "name" : "profile", 878 | "protocol" : "openid-connect", 879 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 880 | "consentRequired" : false, 881 | "config" : { 882 | "userinfo.token.claim" : "true", 883 | "user.attribute" : "profile", 884 | "id.token.claim" : "true", 885 | "access.token.claim" : "true", 886 | "claim.name" : "profile", 887 | "jsonType.label" : "String" 888 | } 889 | }, { 890 | "id" : "a0e08332-954c-46d2-9795-56eb31132580", 891 | "name" : "given name", 892 | "protocol" : "openid-connect", 893 | "protocolMapper" : "oidc-usermodel-property-mapper", 894 | "consentRequired" : false, 895 | "config" : { 896 | "userinfo.token.claim" : "true", 897 | "user.attribute" : "firstName", 898 | "id.token.claim" : "true", 899 | "access.token.claim" : "true", 900 | "claim.name" : "given_name", 901 | "jsonType.label" : "String" 902 | } 903 | }, { 904 | "id" : "cea0cd9c-d085-4d19-acc3-4bb41c891b68", 905 | "name" : "nickname", 906 | "protocol" : "openid-connect", 907 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 908 | "consentRequired" : false, 909 | "config" : { 910 | "userinfo.token.claim" : "true", 911 | "user.attribute" : "nickname", 912 | "id.token.claim" : "true", 913 | "access.token.claim" : "true", 914 | "claim.name" : "nickname", 915 | "jsonType.label" : "String" 916 | } 917 | }, { 918 | "id" : "3122097d-4cba-46c2-8b3b-5d87a4cc605e", 919 | "name" : "middle name", 920 | "protocol" : "openid-connect", 921 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 922 | "consentRequired" : false, 923 | "config" : { 924 | "userinfo.token.claim" : "true", 925 | "user.attribute" : "middleName", 926 | "id.token.claim" : "true", 927 | "access.token.claim" : "true", 928 | "claim.name" : "middle_name", 929 | "jsonType.label" : "String" 930 | } 931 | }, { 932 | "id" : "a3b97897-d913-4e0a-a4cf-033ce78f7d24", 933 | "name" : "username", 934 | "protocol" : "openid-connect", 935 | "protocolMapper" : "oidc-usermodel-property-mapper", 936 | "consentRequired" : false, 937 | "config" : { 938 | "userinfo.token.claim" : "true", 939 | "user.attribute" : "username", 940 | "id.token.claim" : "true", 941 | "access.token.claim" : "true", 942 | "claim.name" : "preferred_username", 943 | "jsonType.label" : "String" 944 | } 945 | }, { 946 | "id" : "a44eeb9d-410d-49c5-b0e0-5d84787627ad", 947 | "name" : "website", 948 | "protocol" : "openid-connect", 949 | "protocolMapper" : "oidc-usermodel-attribute-mapper", 950 | "consentRequired" : false, 951 | "config" : { 952 | "userinfo.token.claim" : "true", 953 | "user.attribute" : "website", 954 | "id.token.claim" : "true", 955 | "access.token.claim" : "true", 956 | "claim.name" : "website", 957 | "jsonType.label" : "String" 958 | } 959 | } ] 960 | }, { 961 | "id" : "651408a7-6704-4198-a60f-988821b633ea", 962 | "name" : "role_list", 963 | "description" : "SAML role list", 964 | "protocol" : "saml", 965 | "attributes" : { 966 | "consent.screen.text" : "${samlRoleListScopeConsentText}", 967 | "display.on.consent.screen" : "true" 968 | }, 969 | "protocolMappers" : [ { 970 | "id" : "a8c56c7b-ccbc-4b01-8df5-3ecb6328755f", 971 | "name" : "role list", 972 | "protocol" : "saml", 973 | "protocolMapper" : "saml-role-list-mapper", 974 | "consentRequired" : false, 975 | "config" : { 976 | "single" : "false", 977 | "attribute.nameformat" : "Basic", 978 | "attribute.name" : "Role" 979 | } 980 | } ] 981 | }, { 982 | "id" : "13ec0fd3-e64a-4d6f-9be7-c8760f2c9d6b", 983 | "name" : "roles", 984 | "description" : "OpenID Connect scope for add user roles to the access token", 985 | "protocol" : "openid-connect", 986 | "attributes" : { 987 | "include.in.token.scope" : "false", 988 | "display.on.consent.screen" : "true", 989 | "consent.screen.text" : "${rolesScopeConsentText}" 990 | }, 991 | "protocolMappers" : [ { 992 | "id" : "75e741f8-dcd5-49d2-815e-8604ec1d08a1", 993 | "name" : "realm roles", 994 | "protocol" : "openid-connect", 995 | "protocolMapper" : "oidc-usermodel-realm-role-mapper", 996 | "consentRequired" : false, 997 | "config" : { 998 | "user.attribute" : "foo", 999 | "access.token.claim" : "true", 1000 | "claim.name" : "realm_access.roles", 1001 | "jsonType.label" : "String", 1002 | "multivalued" : "true" 1003 | } 1004 | }, { 1005 | "id" : "06a2d506-4996-4a33-8c43-2cf64af6a630", 1006 | "name" : "client roles", 1007 | "protocol" : "openid-connect", 1008 | "protocolMapper" : "oidc-usermodel-client-role-mapper", 1009 | "consentRequired" : false, 1010 | "config" : { 1011 | "user.attribute" : "foo", 1012 | "access.token.claim" : "true", 1013 | "claim.name" : "resource_access.${client_id}.roles", 1014 | "jsonType.label" : "String", 1015 | "multivalued" : "true" 1016 | } 1017 | }, { 1018 | "id" : "3c3470df-d414-4e1c-87fc-3fb3cea34b8d", 1019 | "name" : "audience resolve", 1020 | "protocol" : "openid-connect", 1021 | "protocolMapper" : "oidc-audience-resolve-mapper", 1022 | "consentRequired" : false, 1023 | "config" : { } 1024 | } ] 1025 | }, { 1026 | "id" : "d85aba25-c74b-49e3-9ccb-77b4bb16efa5", 1027 | "name" : "web-origins", 1028 | "description" : "OpenID Connect scope for add allowed web origins to the access token", 1029 | "protocol" : "openid-connect", 1030 | "attributes" : { 1031 | "include.in.token.scope" : "false", 1032 | "display.on.consent.screen" : "false", 1033 | "consent.screen.text" : "" 1034 | }, 1035 | "protocolMappers" : [ { 1036 | "id" : "86b3f64f-1525-4500-bcbc-9b889b25f995", 1037 | "name" : "allowed web origins", 1038 | "protocol" : "openid-connect", 1039 | "protocolMapper" : "oidc-allowed-origins-mapper", 1040 | "consentRequired" : false, 1041 | "config" : { } 1042 | } ] 1043 | } ], 1044 | "defaultDefaultClientScopes" : [ "roles", "profile", "role_list", "email", "web-origins" ], 1045 | "defaultOptionalClientScopes" : [ "phone", "address", "offline_access", "microprofile-jwt" ], 1046 | "browserSecurityHeaders" : { 1047 | "contentSecurityPolicyReportOnly" : "", 1048 | "xContentTypeOptions" : "nosniff", 1049 | "xRobotsTag" : "none", 1050 | "xFrameOptions" : "SAMEORIGIN", 1051 | "xXSSProtection" : "1; mode=block", 1052 | "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", 1053 | "strictTransportSecurity" : "max-age=31536000; includeSubDomains" 1054 | }, 1055 | "smtpServer" : { }, 1056 | "eventsEnabled" : false, 1057 | "eventsListeners" : [ "jboss-logging" ], 1058 | "enabledEventTypes" : [ ], 1059 | "adminEventsEnabled" : false, 1060 | "adminEventsDetailsEnabled" : false, 1061 | "components" : { 1062 | "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { 1063 | "id" : "59048b39-ad0f-4d12-8c52-7cfc2c43278a", 1064 | "name" : "Allowed Protocol Mapper Types", 1065 | "providerId" : "allowed-protocol-mappers", 1066 | "subType" : "authenticated", 1067 | "subComponents" : { }, 1068 | "config" : { 1069 | "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper", "saml-role-list-mapper", "oidc-address-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper" ] 1070 | } 1071 | }, { 1072 | "id" : "760559a6-a59f-4175-9ac5-6f3612e20129", 1073 | "name" : "Trusted Hosts", 1074 | "providerId" : "trusted-hosts", 1075 | "subType" : "anonymous", 1076 | "subComponents" : { }, 1077 | "config" : { 1078 | "host-sending-registration-request-must-match" : [ "true" ], 1079 | "client-uris-must-match" : [ "true" ] 1080 | } 1081 | }, { 1082 | "id" : "24f4cb42-76bd-499e-812a-4e0d270c9e13", 1083 | "name" : "Full Scope Disabled", 1084 | "providerId" : "scope", 1085 | "subType" : "anonymous", 1086 | "subComponents" : { }, 1087 | "config" : { } 1088 | }, { 1089 | "id" : "abbfc599-480a-44ef-8e33-73a83eaab166", 1090 | "name" : "Allowed Protocol Mapper Types", 1091 | "providerId" : "allowed-protocol-mappers", 1092 | "subType" : "anonymous", 1093 | "subComponents" : { }, 1094 | "config" : { 1095 | "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper" ] 1096 | } 1097 | }, { 1098 | "id" : "3c6450f0-4521-402b-a247-c8165854b1fa", 1099 | "name" : "Allowed Client Scopes", 1100 | "providerId" : "allowed-client-templates", 1101 | "subType" : "anonymous", 1102 | "subComponents" : { }, 1103 | "config" : { 1104 | "allow-default-scopes" : [ "true" ] 1105 | } 1106 | }, { 1107 | "id" : "d9b64399-744b-498e-9d35-f68b1582bd7d", 1108 | "name" : "Consent Required", 1109 | "providerId" : "consent-required", 1110 | "subType" : "anonymous", 1111 | "subComponents" : { }, 1112 | "config" : { } 1113 | }, { 1114 | "id" : "22f15f1f-3116-4348-a1e5-fc0d7576452a", 1115 | "name" : "Max Clients Limit", 1116 | "providerId" : "max-clients", 1117 | "subType" : "anonymous", 1118 | "subComponents" : { }, 1119 | "config" : { 1120 | "max-clients" : [ "200" ] 1121 | } 1122 | }, { 1123 | "id" : "4ad7b291-ddbb-4674-8c3d-ab8fd76d4168", 1124 | "name" : "Allowed Client Scopes", 1125 | "providerId" : "allowed-client-templates", 1126 | "subType" : "authenticated", 1127 | "subComponents" : { }, 1128 | "config" : { 1129 | "allow-default-scopes" : [ "true" ] 1130 | } 1131 | } ], 1132 | "org.keycloak.keys.KeyProvider" : [ { 1133 | "id" : "f71cc325-9907-4d27-a0e6-88fca7450e5e", 1134 | "name" : "aes-generated", 1135 | "providerId" : "aes-generated", 1136 | "subComponents" : { }, 1137 | "config" : { 1138 | "kid" : [ "6c7d982e-372f-49c6-a4f3-5c451fb85eca" ], 1139 | "secret" : [ "yH6M3W7aOgh2_cKJ0srWbw" ], 1140 | "priority" : [ "100" ] 1141 | } 1142 | }, { 1143 | "id" : "7b50d0ab-dda5-4624-aa42-b4b397724ce1", 1144 | "name" : "hmac-generated", 1145 | "providerId" : "hmac-generated", 1146 | "subComponents" : { }, 1147 | "config" : { 1148 | "kid" : [ "587f0fb5-845d-4b45-87a0-84145092aaef" ], 1149 | "secret" : [ "PuH8Lxh9GeNfGJRDk34SWIlBDdrJpC3U3SfcxqqQtlIf2DBzRKUu8VbDVrmMN5b5CoPsJhrQ2SVb-iE9Lzsb3A" ], 1150 | "priority" : [ "100" ], 1151 | "algorithm" : [ "HS256" ] 1152 | } 1153 | }, { 1154 | "id" : "547c1c71-9f97-4e12-801b-ed5c2cc61bba", 1155 | "name" : "rsa-generated", 1156 | "providerId" : "rsa-generated", 1157 | "subComponents" : { }, 1158 | "config" : { 1159 | "privateKey" : [ "MIIEowIBAAKCAQEAjdo2HZ5ruNnIbkSeAfFYpbPvJw3vtz/VuKJerC4mUXYd7qRMhs3VLJZ3mFyeCuO8W81vkGrFiC9KQnX2lHj2dtA/RWEJw5bpz+JdOFr5pvXg0lQ0sa+hro9afWDygTU4FmLsEi5z98847TbH178RT6n7+JVqZ9jYU9rSpwVTC8E/4yxSuStmhGCcAkZ6dGhHNBdvGUgwxKYj7dYLRJiI+nilIdKuxPzxI/YZxZnXBHDdbNXJgDymTQPut99OnBxeZbH38CJ1MNo3VdV1fzOMGUHe+vn/EOD5E+pXC8PwvJnWU+XHUTFVZeyIXehh3pYLUsq/6bZ1MYsEaFIhznOkwwIDAQABAoIBAHB+64fVyUxRurhoRn737fuLlU/9p2xGfbHtYvNdrhnQeLB3MBGAT10K/1Gfsd6k+Q49AAsiAgGcr2HBt4nL3HohcOwOpvWsS0UIGjHFRFP6jw9+pEN+K9UJ7xObvPZnRFHMpbdNi76tYlINrbMV3h61ihR8OmSc/gKSeZjnihK5OkaNnlqGRaBM/koI+iAxUHuJPnBLBZmD4T8eIfE4S2TvUeVeQogI9Muvnb9tIPJ5XyP9iXWLdRjnek/+wTdxHHZuo06Tc0bMjRaTHiF6K9ntOM2EmQb6bS2J47zgzRLNFE22BWH7RJq659EzElkOn0C0k7dWDTur/3Lpx1+zxJECgYEA8t+J3J+9oGTFmY2VPH05Yr/R/iVDOyIOlO1CmgonOQ3KPhbfNBB3aTAJP20LOZChN4JoWuiZJg4UwzXOeY9DvdDkPO0YLlSjPAIwJNk+xcxFcp5hqMUul2db+cgEY8zp0Wg9kFOq3JmJjK4+1+fgsVnOB+B08ZYI6bZzsUVKzucCgYEAlYTrsxs6fQua0cvZNQPYNZzwF3LVwPBl/ntkdRBE3HGyZeCAhIj7e9pAUusCPsQJaCmW2UEmywD/aIxGrBkojzTKItshM3PN1PYKL8W0Zq+H67uF5KfdvsbabZWHfP/LGCpoKF8Ov7JVPPqGrZ03Z2SheeLZAtNeHN4OB1u9i8UCgYATkS7qN3Rvl67T0DRVy0D0U7/3Wckw2m2SUgsrneXLEvFYTz9sUmdMcjJMidx9pslWT4tYx6SPDFNf5tXbtU8f29SHlBJ+qRL9oq9+SIJmLS7rLRdxIXG/gPRIC3VPFRNBa8SJ/DOn0jbivqcRffz8TN/sgojpbc0KB0kK3ypHwQKBgCKVCcb1R0PgyUA4+9YNO5a647UotFPZxl1jwMpqpuKt0WtKz67X2AK/ah1DidNmmB5lcCRzsztE0c4mk7n+X6kvtoj1UeqKoFLfTV/bRGxzsOZPCxrl0J3tdFvgN+QrbZf7Rvf/dHPWFWzzLO8+66+YUNjWJQdIR/45Rdlh2KdZAoGBAMfF3ir+fe3KdQ6hAf9QyrLxJ5l+GO+IgtxXGbon7eeJBIZHHdMeDy4pC7DMcI214BmIntbyY+xS+gI3oM26EJUVmrZ6tkyIDFsCHm9rcXG9ogvffzQWM1Wqzm27hR/3s+EPWW9AOcIimiFV1UPp/mLjnrCuq58V2aJS/TT14oLe" ], 1160 | "certificate" : [ "MIICmzCCAYMCBgFygL/j4DANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjAwNjA0MTkxMDU4WhcNMzAwNjA0MTkxMjM4WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCN2jYdnmu42chuRJ4B8Vils+8nDe+3P9W4ol6sLiZRdh3upEyGzdUslneYXJ4K47xbzW+QasWIL0pCdfaUePZ20D9FYQnDlunP4l04Wvmm9eDSVDSxr6Guj1p9YPKBNTgWYuwSLnP3zzjtNsfXvxFPqfv4lWpn2NhT2tKnBVMLwT/jLFK5K2aEYJwCRnp0aEc0F28ZSDDEpiPt1gtEmIj6eKUh0q7E/PEj9hnFmdcEcN1s1cmAPKZNA+63306cHF5lsffwInUw2jdV1XV/M4wZQd76+f8Q4PkT6lcLw/C8mdZT5cdRMVVl7Ihd6GHelgtSyr/ptnUxiwRoUiHOc6TDAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIAqydMYxa51kNEyfXyR2kStlglE4LDeLBLHDABeBPE0eN2awoH/mw3kXS4OA/C0e3c7bAwViOzOVERGeUNiBvP5rL1Amuu97nwFcxhkTaJH4ZwCGkxceaIo9LNDpAEesqHLQSdplFXIA4TbEFoKMem4k31KVU7i9/rUesrSRmxLptIOK7LLvRMYiY/t7tdAvoZAtoliuQlFKQywEuxXQrCkcoVEAARABWGt0rsWC2xK0tVxHRIrENwvMp/aUYd17sZ0403aaS9dlvfQ63ExnaHd+++RJtPku8P220Tw27YVmFAwzJgS0aUpEaDsgRNz6OMSyxEg/n7eKK08aU3szwQ=" ], 1161 | "priority" : [ "100" ] 1162 | } 1163 | } ] 1164 | }, 1165 | "internationalizationEnabled" : false, 1166 | "supportedLocales" : [ ], 1167 | "authenticationFlows" : [ { 1168 | "id" : "3253f9b7-905d-4458-ad8a-8ada5e16d195", 1169 | "alias" : "Account verification options", 1170 | "description" : "Method with which to verity the existing account", 1171 | "providerId" : "basic-flow", 1172 | "topLevel" : false, 1173 | "builtIn" : true, 1174 | "authenticationExecutions" : [ { 1175 | "authenticator" : "idp-email-verification", 1176 | "requirement" : "ALTERNATIVE", 1177 | "priority" : 10, 1178 | "userSetupAllowed" : false, 1179 | "autheticatorFlow" : false 1180 | }, { 1181 | "requirement" : "ALTERNATIVE", 1182 | "priority" : 20, 1183 | "flowAlias" : "Verify Existing Account by Re-authentication", 1184 | "userSetupAllowed" : false, 1185 | "autheticatorFlow" : true 1186 | } ] 1187 | }, { 1188 | "id" : "75bd854e-ab99-46f1-90ed-a8bfc1559558", 1189 | "alias" : "Authentication Options", 1190 | "description" : "Authentication options.", 1191 | "providerId" : "basic-flow", 1192 | "topLevel" : false, 1193 | "builtIn" : true, 1194 | "authenticationExecutions" : [ { 1195 | "authenticator" : "basic-auth", 1196 | "requirement" : "REQUIRED", 1197 | "priority" : 10, 1198 | "userSetupAllowed" : false, 1199 | "autheticatorFlow" : false 1200 | }, { 1201 | "authenticator" : "basic-auth-otp", 1202 | "requirement" : "DISABLED", 1203 | "priority" : 20, 1204 | "userSetupAllowed" : false, 1205 | "autheticatorFlow" : false 1206 | }, { 1207 | "authenticator" : "auth-spnego", 1208 | "requirement" : "DISABLED", 1209 | "priority" : 30, 1210 | "userSetupAllowed" : false, 1211 | "autheticatorFlow" : false 1212 | } ] 1213 | }, { 1214 | "id" : "9b0e6cce-62c5-4fb6-a48d-e07c950e38c3", 1215 | "alias" : "Browser - Conditional OTP", 1216 | "description" : "Flow to determine if the OTP is required for the authentication", 1217 | "providerId" : "basic-flow", 1218 | "topLevel" : false, 1219 | "builtIn" : true, 1220 | "authenticationExecutions" : [ { 1221 | "authenticator" : "conditional-user-configured", 1222 | "requirement" : "REQUIRED", 1223 | "priority" : 10, 1224 | "userSetupAllowed" : false, 1225 | "autheticatorFlow" : false 1226 | }, { 1227 | "authenticator" : "auth-otp-form", 1228 | "requirement" : "REQUIRED", 1229 | "priority" : 20, 1230 | "userSetupAllowed" : false, 1231 | "autheticatorFlow" : false 1232 | } ] 1233 | }, { 1234 | "id" : "1c26fd14-ac06-4dc1-bdd8-8c34c1b41720", 1235 | "alias" : "Direct Grant - Conditional OTP", 1236 | "description" : "Flow to determine if the OTP is required for the authentication", 1237 | "providerId" : "basic-flow", 1238 | "topLevel" : false, 1239 | "builtIn" : true, 1240 | "authenticationExecutions" : [ { 1241 | "authenticator" : "conditional-user-configured", 1242 | "requirement" : "REQUIRED", 1243 | "priority" : 10, 1244 | "userSetupAllowed" : false, 1245 | "autheticatorFlow" : false 1246 | }, { 1247 | "authenticator" : "direct-grant-validate-otp", 1248 | "requirement" : "REQUIRED", 1249 | "priority" : 20, 1250 | "userSetupAllowed" : false, 1251 | "autheticatorFlow" : false 1252 | } ] 1253 | }, { 1254 | "id" : "254f7549-51ec-4565-a736-35c07b6e25f0", 1255 | "alias" : "First broker login - Conditional OTP", 1256 | "description" : "Flow to determine if the OTP is required for the authentication", 1257 | "providerId" : "basic-flow", 1258 | "topLevel" : false, 1259 | "builtIn" : true, 1260 | "authenticationExecutions" : [ { 1261 | "authenticator" : "conditional-user-configured", 1262 | "requirement" : "REQUIRED", 1263 | "priority" : 10, 1264 | "userSetupAllowed" : false, 1265 | "autheticatorFlow" : false 1266 | }, { 1267 | "authenticator" : "auth-otp-form", 1268 | "requirement" : "REQUIRED", 1269 | "priority" : 20, 1270 | "userSetupAllowed" : false, 1271 | "autheticatorFlow" : false 1272 | } ] 1273 | }, { 1274 | "id" : "b2413da8-3de9-4bfe-b77e-643fd1964c8f", 1275 | "alias" : "Handle Existing Account", 1276 | "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", 1277 | "providerId" : "basic-flow", 1278 | "topLevel" : false, 1279 | "builtIn" : true, 1280 | "authenticationExecutions" : [ { 1281 | "authenticator" : "idp-confirm-link", 1282 | "requirement" : "REQUIRED", 1283 | "priority" : 10, 1284 | "userSetupAllowed" : false, 1285 | "autheticatorFlow" : false 1286 | }, { 1287 | "requirement" : "REQUIRED", 1288 | "priority" : 20, 1289 | "flowAlias" : "Account verification options", 1290 | "userSetupAllowed" : false, 1291 | "autheticatorFlow" : true 1292 | } ] 1293 | }, { 1294 | "id" : "f8392bfb-8dce-4a16-8af1-b2a4d1a0a273", 1295 | "alias" : "Reset - Conditional OTP", 1296 | "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", 1297 | "providerId" : "basic-flow", 1298 | "topLevel" : false, 1299 | "builtIn" : true, 1300 | "authenticationExecutions" : [ { 1301 | "authenticator" : "conditional-user-configured", 1302 | "requirement" : "REQUIRED", 1303 | "priority" : 10, 1304 | "userSetupAllowed" : false, 1305 | "autheticatorFlow" : false 1306 | }, { 1307 | "authenticator" : "reset-otp", 1308 | "requirement" : "REQUIRED", 1309 | "priority" : 20, 1310 | "userSetupAllowed" : false, 1311 | "autheticatorFlow" : false 1312 | } ] 1313 | }, { 1314 | "id" : "fb69c297-b26e-44fa-aabd-d7b40eec3cd3", 1315 | "alias" : "User creation or linking", 1316 | "description" : "Flow for the existing/non-existing user alternatives", 1317 | "providerId" : "basic-flow", 1318 | "topLevel" : false, 1319 | "builtIn" : true, 1320 | "authenticationExecutions" : [ { 1321 | "authenticatorConfig" : "create unique user config", 1322 | "authenticator" : "idp-create-user-if-unique", 1323 | "requirement" : "ALTERNATIVE", 1324 | "priority" : 10, 1325 | "userSetupAllowed" : false, 1326 | "autheticatorFlow" : false 1327 | }, { 1328 | "requirement" : "ALTERNATIVE", 1329 | "priority" : 20, 1330 | "flowAlias" : "Handle Existing Account", 1331 | "userSetupAllowed" : false, 1332 | "autheticatorFlow" : true 1333 | } ] 1334 | }, { 1335 | "id" : "de3a41a9-7018-4931-9c4d-d04f9501b2ce", 1336 | "alias" : "Verify Existing Account by Re-authentication", 1337 | "description" : "Reauthentication of existing account", 1338 | "providerId" : "basic-flow", 1339 | "topLevel" : false, 1340 | "builtIn" : true, 1341 | "authenticationExecutions" : [ { 1342 | "authenticator" : "idp-username-password-form", 1343 | "requirement" : "REQUIRED", 1344 | "priority" : 10, 1345 | "userSetupAllowed" : false, 1346 | "autheticatorFlow" : false 1347 | }, { 1348 | "requirement" : "CONDITIONAL", 1349 | "priority" : 20, 1350 | "flowAlias" : "First broker login - Conditional OTP", 1351 | "userSetupAllowed" : false, 1352 | "autheticatorFlow" : true 1353 | } ] 1354 | }, { 1355 | "id" : "6526b0d1-b48e-46c6-bb08-11ebcf458def", 1356 | "alias" : "browser", 1357 | "description" : "browser based authentication", 1358 | "providerId" : "basic-flow", 1359 | "topLevel" : true, 1360 | "builtIn" : true, 1361 | "authenticationExecutions" : [ { 1362 | "authenticator" : "auth-cookie", 1363 | "requirement" : "ALTERNATIVE", 1364 | "priority" : 10, 1365 | "userSetupAllowed" : false, 1366 | "autheticatorFlow" : false 1367 | }, { 1368 | "authenticator" : "auth-spnego", 1369 | "requirement" : "DISABLED", 1370 | "priority" : 20, 1371 | "userSetupAllowed" : false, 1372 | "autheticatorFlow" : false 1373 | }, { 1374 | "authenticator" : "identity-provider-redirector", 1375 | "requirement" : "ALTERNATIVE", 1376 | "priority" : 25, 1377 | "userSetupAllowed" : false, 1378 | "autheticatorFlow" : false 1379 | }, { 1380 | "requirement" : "ALTERNATIVE", 1381 | "priority" : 30, 1382 | "flowAlias" : "forms", 1383 | "userSetupAllowed" : false, 1384 | "autheticatorFlow" : true 1385 | } ] 1386 | }, { 1387 | "id" : "92a653ba-8f2d-4283-8354-ca55f9d89181", 1388 | "alias" : "clients", 1389 | "description" : "Base authentication for clients", 1390 | "providerId" : "client-flow", 1391 | "topLevel" : true, 1392 | "builtIn" : true, 1393 | "authenticationExecutions" : [ { 1394 | "authenticator" : "client-secret", 1395 | "requirement" : "ALTERNATIVE", 1396 | "priority" : 10, 1397 | "userSetupAllowed" : false, 1398 | "autheticatorFlow" : false 1399 | }, { 1400 | "authenticator" : "client-jwt", 1401 | "requirement" : "ALTERNATIVE", 1402 | "priority" : 20, 1403 | "userSetupAllowed" : false, 1404 | "autheticatorFlow" : false 1405 | }, { 1406 | "authenticator" : "client-secret-jwt", 1407 | "requirement" : "ALTERNATIVE", 1408 | "priority" : 30, 1409 | "userSetupAllowed" : false, 1410 | "autheticatorFlow" : false 1411 | }, { 1412 | "authenticator" : "client-x509", 1413 | "requirement" : "ALTERNATIVE", 1414 | "priority" : 40, 1415 | "userSetupAllowed" : false, 1416 | "autheticatorFlow" : false 1417 | } ] 1418 | }, { 1419 | "id" : "e365be39-78db-46f0-b2e8-4e7001c2f5d0", 1420 | "alias" : "direct grant", 1421 | "description" : "OpenID Connect Resource Owner Grant", 1422 | "providerId" : "basic-flow", 1423 | "topLevel" : true, 1424 | "builtIn" : true, 1425 | "authenticationExecutions" : [ { 1426 | "authenticator" : "direct-grant-validate-username", 1427 | "requirement" : "REQUIRED", 1428 | "priority" : 10, 1429 | "userSetupAllowed" : false, 1430 | "autheticatorFlow" : false 1431 | }, { 1432 | "authenticator" : "direct-grant-validate-password", 1433 | "requirement" : "REQUIRED", 1434 | "priority" : 20, 1435 | "userSetupAllowed" : false, 1436 | "autheticatorFlow" : false 1437 | }, { 1438 | "requirement" : "CONDITIONAL", 1439 | "priority" : 30, 1440 | "flowAlias" : "Direct Grant - Conditional OTP", 1441 | "userSetupAllowed" : false, 1442 | "autheticatorFlow" : true 1443 | } ] 1444 | }, { 1445 | "id" : "dd61caf5-a40f-48b7-9e8c-a1f3b67041dd", 1446 | "alias" : "docker auth", 1447 | "description" : "Used by Docker clients to authenticate against the IDP", 1448 | "providerId" : "basic-flow", 1449 | "topLevel" : true, 1450 | "builtIn" : true, 1451 | "authenticationExecutions" : [ { 1452 | "authenticator" : "docker-http-basic-authenticator", 1453 | "requirement" : "REQUIRED", 1454 | "priority" : 10, 1455 | "userSetupAllowed" : false, 1456 | "autheticatorFlow" : false 1457 | } ] 1458 | }, { 1459 | "id" : "7a055643-62e1-4ac1-b126-9a8d6c299635", 1460 | "alias" : "first broker login", 1461 | "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", 1462 | "providerId" : "basic-flow", 1463 | "topLevel" : true, 1464 | "builtIn" : true, 1465 | "authenticationExecutions" : [ { 1466 | "authenticatorConfig" : "review profile config", 1467 | "authenticator" : "idp-review-profile", 1468 | "requirement" : "REQUIRED", 1469 | "priority" : 10, 1470 | "userSetupAllowed" : false, 1471 | "autheticatorFlow" : false 1472 | }, { 1473 | "requirement" : "REQUIRED", 1474 | "priority" : 20, 1475 | "flowAlias" : "User creation or linking", 1476 | "userSetupAllowed" : false, 1477 | "autheticatorFlow" : true 1478 | } ] 1479 | }, { 1480 | "id" : "fe8bc7ee-6e8f-436e-8336-c60fcd350843", 1481 | "alias" : "forms", 1482 | "description" : "Username, password, otp and other auth forms.", 1483 | "providerId" : "basic-flow", 1484 | "topLevel" : false, 1485 | "builtIn" : true, 1486 | "authenticationExecutions" : [ { 1487 | "authenticator" : "auth-username-password-form", 1488 | "requirement" : "REQUIRED", 1489 | "priority" : 10, 1490 | "userSetupAllowed" : false, 1491 | "autheticatorFlow" : false 1492 | }, { 1493 | "requirement" : "CONDITIONAL", 1494 | "priority" : 20, 1495 | "flowAlias" : "Browser - Conditional OTP", 1496 | "userSetupAllowed" : false, 1497 | "autheticatorFlow" : true 1498 | } ] 1499 | }, { 1500 | "id" : "3646f08e-ab70-415b-a701-6ed2e2d214c9", 1501 | "alias" : "http challenge", 1502 | "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", 1503 | "providerId" : "basic-flow", 1504 | "topLevel" : true, 1505 | "builtIn" : true, 1506 | "authenticationExecutions" : [ { 1507 | "authenticator" : "no-cookie-redirect", 1508 | "requirement" : "REQUIRED", 1509 | "priority" : 10, 1510 | "userSetupAllowed" : false, 1511 | "autheticatorFlow" : false 1512 | }, { 1513 | "requirement" : "REQUIRED", 1514 | "priority" : 20, 1515 | "flowAlias" : "Authentication Options", 1516 | "userSetupAllowed" : false, 1517 | "autheticatorFlow" : true 1518 | } ] 1519 | }, { 1520 | "id" : "04176530-0972-47ad-83df-19d8534caac2", 1521 | "alias" : "registration", 1522 | "description" : "registration flow", 1523 | "providerId" : "basic-flow", 1524 | "topLevel" : true, 1525 | "builtIn" : true, 1526 | "authenticationExecutions" : [ { 1527 | "authenticator" : "registration-page-form", 1528 | "requirement" : "REQUIRED", 1529 | "priority" : 10, 1530 | "flowAlias" : "registration form", 1531 | "userSetupAllowed" : false, 1532 | "autheticatorFlow" : true 1533 | } ] 1534 | }, { 1535 | "id" : "fa0ed569-6746-439e-b07e-89f7ed918c07", 1536 | "alias" : "registration form", 1537 | "description" : "registration form", 1538 | "providerId" : "form-flow", 1539 | "topLevel" : false, 1540 | "builtIn" : true, 1541 | "authenticationExecutions" : [ { 1542 | "authenticator" : "registration-user-creation", 1543 | "requirement" : "REQUIRED", 1544 | "priority" : 20, 1545 | "userSetupAllowed" : false, 1546 | "autheticatorFlow" : false 1547 | }, { 1548 | "authenticator" : "registration-profile-action", 1549 | "requirement" : "REQUIRED", 1550 | "priority" : 40, 1551 | "userSetupAllowed" : false, 1552 | "autheticatorFlow" : false 1553 | }, { 1554 | "authenticator" : "registration-password-action", 1555 | "requirement" : "REQUIRED", 1556 | "priority" : 50, 1557 | "userSetupAllowed" : false, 1558 | "autheticatorFlow" : false 1559 | }, { 1560 | "authenticator" : "registration-recaptcha-action", 1561 | "requirement" : "DISABLED", 1562 | "priority" : 60, 1563 | "userSetupAllowed" : false, 1564 | "autheticatorFlow" : false 1565 | } ] 1566 | }, { 1567 | "id" : "03680917-28f3-4ccd-bdf6-4a516f7c0018", 1568 | "alias" : "reset credentials", 1569 | "description" : "Reset credentials for a user if they forgot their password or something", 1570 | "providerId" : "basic-flow", 1571 | "topLevel" : true, 1572 | "builtIn" : true, 1573 | "authenticationExecutions" : [ { 1574 | "authenticator" : "reset-credentials-choose-user", 1575 | "requirement" : "REQUIRED", 1576 | "priority" : 10, 1577 | "userSetupAllowed" : false, 1578 | "autheticatorFlow" : false 1579 | }, { 1580 | "authenticator" : "reset-credential-email", 1581 | "requirement" : "REQUIRED", 1582 | "priority" : 20, 1583 | "userSetupAllowed" : false, 1584 | "autheticatorFlow" : false 1585 | }, { 1586 | "authenticator" : "reset-password", 1587 | "requirement" : "REQUIRED", 1588 | "priority" : 30, 1589 | "userSetupAllowed" : false, 1590 | "autheticatorFlow" : false 1591 | }, { 1592 | "requirement" : "CONDITIONAL", 1593 | "priority" : 40, 1594 | "flowAlias" : "Reset - Conditional OTP", 1595 | "userSetupAllowed" : false, 1596 | "autheticatorFlow" : true 1597 | } ] 1598 | }, { 1599 | "id" : "19a9d9aa-2d2b-4701-807f-c384ab921c7e", 1600 | "alias" : "saml ecp", 1601 | "description" : "SAML ECP Profile Authentication Flow", 1602 | "providerId" : "basic-flow", 1603 | "topLevel" : true, 1604 | "builtIn" : true, 1605 | "authenticationExecutions" : [ { 1606 | "authenticator" : "http-basic-authenticator", 1607 | "requirement" : "REQUIRED", 1608 | "priority" : 10, 1609 | "userSetupAllowed" : false, 1610 | "autheticatorFlow" : false 1611 | } ] 1612 | } ], 1613 | "authenticatorConfig" : [ { 1614 | "id" : "534f01f4-45b3-43a0-91d1-238860cc126d", 1615 | "alias" : "create unique user config", 1616 | "config" : { 1617 | "require.password.update.after.registration" : "false" 1618 | } 1619 | }, { 1620 | "id" : "65bb9337-9633-4a21-8f6f-1d4129f664ac", 1621 | "alias" : "review profile config", 1622 | "config" : { 1623 | "update.profile.on.first.login" : "missing" 1624 | } 1625 | } ], 1626 | "requiredActions" : [ { 1627 | "alias" : "CONFIGURE_TOTP", 1628 | "name" : "Configure OTP", 1629 | "providerId" : "CONFIGURE_TOTP", 1630 | "enabled" : true, 1631 | "defaultAction" : false, 1632 | "priority" : 10, 1633 | "config" : { } 1634 | }, { 1635 | "alias" : "terms_and_conditions", 1636 | "name" : "Terms and Conditions", 1637 | "providerId" : "terms_and_conditions", 1638 | "enabled" : false, 1639 | "defaultAction" : false, 1640 | "priority" : 20, 1641 | "config" : { } 1642 | }, { 1643 | "alias" : "UPDATE_PASSWORD", 1644 | "name" : "Update Password", 1645 | "providerId" : "UPDATE_PASSWORD", 1646 | "enabled" : true, 1647 | "defaultAction" : false, 1648 | "priority" : 30, 1649 | "config" : { } 1650 | }, { 1651 | "alias" : "UPDATE_PROFILE", 1652 | "name" : "Update Profile", 1653 | "providerId" : "UPDATE_PROFILE", 1654 | "enabled" : true, 1655 | "defaultAction" : false, 1656 | "priority" : 40, 1657 | "config" : { } 1658 | }, { 1659 | "alias" : "VERIFY_EMAIL", 1660 | "name" : "Verify Email", 1661 | "providerId" : "VERIFY_EMAIL", 1662 | "enabled" : true, 1663 | "defaultAction" : false, 1664 | "priority" : 50, 1665 | "config" : { } 1666 | }, { 1667 | "alias" : "update_user_locale", 1668 | "name" : "Update User Locale", 1669 | "providerId" : "update_user_locale", 1670 | "enabled" : true, 1671 | "defaultAction" : false, 1672 | "priority" : 1000, 1673 | "config" : { } 1674 | } ], 1675 | "browserFlow" : "browser", 1676 | "registrationFlow" : "registration", 1677 | "directGrantFlow" : "direct grant", 1678 | "resetCredentialsFlow" : "reset credentials", 1679 | "clientAuthenticationFlow" : "clients", 1680 | "dockerAuthenticationFlow" : "docker auth", 1681 | "attributes" : { }, 1682 | "keycloakVersion" : "10.0.0", 1683 | "userManagedAccessAllowed" : false 1684 | } 1685 | -------------------------------------------------------------------------------- /config/keycloak-realm-config/master-users-0.json: -------------------------------------------------------------------------------- 1 | { 2 | "realm" : "master", 3 | "users" : [ { 4 | "id" : "3356c0a0-d4d5-4436-9c5a-2299c71c08ec", 5 | "createdTimestamp" : 1591297959169, 6 | "username" : "admin@example.com", 7 | "email" : "admin@example.com", 8 | "enabled" : true, 9 | "totp" : false, 10 | "emailVerified" : true, 11 | "credentials" : [ { 12 | "id" : "a1a06ecd-fdc0-4e67-92cd-2da22d724e32", 13 | "type" : "password", 14 | "createdDate" : 1591297959315, 15 | "secretData" : "{\"value\":\"6rt5zuqHVHopvd0FTFE0CYadXTtzY0mDY2BrqnNQGS51/7DfMJeGgj0roNnGMGvDv30imErNmiSOYl+cL9jiIA==\",\"salt\":\"LI0kqr09JB7J9wvr2Hxzzg==\"}", 16 | "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\"}" 17 | } ], 18 | "disableableCredentialTypes" : [ ], 19 | "requiredActions" : [ ], 20 | "realmRoles" : [ "offline_access", "admin", "uma_authorization" ], 21 | "clientRoles" : { 22 | "account" : [ "view-profile", "manage-account" ] 23 | }, 24 | "notBefore" : 0, 25 | "groups" : [ ] 26 | } ] 27 | } 28 | -------------------------------------------------------------------------------- /config/keycloak.env: -------------------------------------------------------------------------------- 1 | TZ=Europe/Budapest 2 | DB_VENDOR=POSTGRES 3 | DB_ADDR=postgres 4 | DB_DATABASE=judo 5 | DB_USER=judo 6 | DB_SCHEMA=public 7 | DB_PASSWORD=judo 8 | KEYCLOAK_USER=admin 9 | KEYCLOAK_PASSWORD=judo 10 | PROXY_ADDRESS_FORWARDING=true 11 | -------------------------------------------------------------------------------- /config/postgres.env: -------------------------------------------------------------------------------- 1 | TZ=Europe/Budapest 2 | POSTGRES_DB=judo 3 | POSTGRES_USER=judo 4 | POSTGRES_PASSWORD=judo 5 | -------------------------------------------------------------------------------- /config/traefik/dynamic_conf.toml: -------------------------------------------------------------------------------- 1 | [tls.stores] 2 | [tls.stores.default] 3 | [tls.stores.default.defaultCertificate] 4 | certFile = "/etc/cert/cert.pem" 5 | keyFile = "/etc/cert/key.pem" 6 | 7 | [http.routers] 8 | [http.routers.https-only] 9 | entryPoints = ["http"] 10 | middlewares = ["httpsredirect"] 11 | rule = "HostRegexp(`{host:.+}`)" 12 | service = "noop" 13 | 14 | [http.services] 15 | [http.services.noop.loadBalancer] 16 | [[http.services.noop.loadBalancer.servers]] 17 | url = "http://192.168.0.1" 18 | 19 | [http.middlewares] 20 | [http.middlewares.sso.forwardAuth] 21 | address = "http://traefik-fa:4181" 22 | authResponseHeaders = ["X-Forwarded-User", "X-WebAuth-User"] 23 | trustForwardHeader = "true" 24 | [http.middlewares.httpsredirect.redirectScheme] 25 | scheme = "https" 26 | -------------------------------------------------------------------------------- /config/traefik/forward.ini: -------------------------------------------------------------------------------- 1 | # https://carey.li/2019/10/01/traefik-2-sso-ssl/ 2 | default-provider = oidc 3 | 4 | # Cookie signing nonce, replace this with something random 5 | secret = secret-nonce 6 | 7 | # This client id / secret is defined in keycloak-realm-config/master-realm.json 8 | providers.oidc.client-id = oauth-proxy 9 | providers.oidc.client-secret = 72341b6d-7065-4518-a0e4-50ee15025608 10 | providers.oidc.issuer-url = https://keycloak.localtest.me/auth/realms/master 11 | 12 | log-level = debug 13 | 14 | # Replace demo.carey.li with your own ${TRAEFIK_DOMAIN} 15 | cookie-domain = localtest.me 16 | auth-host = auth.localtest.me 17 | 18 | # Add authorized users here 19 | whitelist = admin@example.com 20 | -------------------------------------------------------------------------------- /config/traefik/traefik.toml: -------------------------------------------------------------------------------- 1 | [log] 2 | level = "DEBUG" 3 | filePath = "/logs/traefik.log" 4 | 5 | [entryPoints] 6 | [entryPoints.http] 7 | address = ":80" 8 | [entryPoints.https] 9 | address = ":443" 10 | 11 | [api] 12 | dashboard = true 13 | insecure = true 14 | 15 | [providers] 16 | [providers.file] 17 | filename = "/etc/traefik/dynamic_conf.toml" 18 | [providers.docker] 19 | endpoint = "unix:///var/run/docker.sock" 20 | watch = true 21 | exposedbydefault = false 22 | defaultrule = "Host(`{{ .Name }}.localtest.me`)" 23 | 24 | [accessLog] 25 | filePath = "/logs/access.log" 26 | 27 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | networks: 4 | judo: {} 5 | 6 | 7 | services: 8 | 9 | # ############################################################################# 10 | # Trafik (Load Balancer) 11 | # Listen onm 80 and 443. 12 | # It redirect xxxx.${DOMAIN} to the host of the given subdomain. 13 | # It allows to use http / https schema, but http is redirected to https. 14 | # ############################################################################# 15 | 16 | traefik: 17 | image: traefik 18 | restart: unless-stopped 19 | container_name: ${COMPOSE_PROJECT_NAME}_traefik 20 | 21 | ports: 22 | - "0.0.0.0:80:80" 23 | - "0.0.0.0:443:443" 24 | volumes: 25 | - /var/run/docker.sock:/var/run/docker.sock:ro 26 | - ./config/traefik:/etc/traefik 27 | - ./.data/traefik/logs:/logs 28 | - ./cert/_.${DOMAIN}:/etc/cert 29 | 30 | environment: 31 | - TZ=Europe/Budapest 32 | 33 | networks: 34 | judo: 35 | aliases: 36 | - traefik.${DOMAIN} 37 | 38 | labels: 39 | - traefik.enable=true 40 | - traefik.backend=traefik-api 41 | - traefik.docker.network=${COMPOSE_PROJECT_NAME}_judo 42 | - traefik.http.services.traefik.loadbalancer.server.port=8080 43 | 44 | # SSL configuration 45 | - traefik.http.routers.traefik-ssl.entryPoints=https 46 | - traefik.http.routers.traefik-ssl.rule=host(`traefik.${DOMAIN}`) 47 | - traefik.http.routers.traefik-ssl.middlewares=sso@file 48 | - traefik.http.routers.traefik-ssl.tls=true 49 | 50 | # depends_on: 51 | # traefik-fa: 52 | # condition: service_healthy 53 | 54 | # ############################################################################# 55 | # Traefik Forward Auth (SSO) 56 | # When sso@file middleware is given to a service, have to be authenticated. 57 | # ############################################################################# 58 | 59 | traefik-fa: 60 | image: thomseddon/traefik-forward-auth 61 | container_name: ${COMPOSE_PROJECT_NAME}_traefik-fa 62 | restart: unless-stopped 63 | 64 | volumes: 65 | - ./config/traefik/forward.ini:/forward.ini 66 | - ./cert/minica.pem:/etc/ssl/certs/ca-certificates.crt 67 | 68 | environment: 69 | - CONFIG=/forward.ini 70 | 71 | dns_search: ${DOMAIN} 72 | networks: 73 | judo: 74 | aliases: 75 | - auth.${DOMAIN} 76 | 77 | labels: 78 | - traefik.enable=true 79 | - traefik.docker.network=${COMPOSE_PROJECT_NAME}_judo 80 | - traefik.backend=traefik-fa 81 | - traefik.http.services.traefik-fa.loadBalancer.server.port=4181 82 | 83 | # SSL configuration 84 | - traefik.http.routers.traefik-fa-ssl.entryPoints=https 85 | - traefik.http.routers.traefik-fa-ssl.rule=host(`auth.${DOMAIN}`) 86 | - traefik.http.routers.traefik-fa-ssl.middlewares=sso@file 87 | - traefik.http.routers.traefik-fa-ssl.tls=true 88 | 89 | depends_on: 90 | keycloak: 91 | condition: service_healthy 92 | 93 | # ############################################################################# 94 | # Who Am I - Test service. It can be called from authentication. 95 | # ############################################################################# 96 | 97 | whoami: 98 | image: emilevauge/whoami 99 | container_name: ${COMPOSE_PROJECT_NAME}_whoami 100 | restart: unless-stopped 101 | networks: 102 | judo: 103 | aliases: 104 | - whoami.${DOMAIN} 105 | 106 | labels: 107 | - traefik.enable=true 108 | - traefik.backend=whoami 109 | - traefik.docker.network=${COMPOSE_PROJECT_NAME}_judo 110 | 111 | # SSL configuration 112 | - traefik.http.routers.whoami.entryPoints=https 113 | - traefik.http.routers.whoami.rule=host(`whoami.${DOMAIN}`) 114 | - traefik.http.routers.whoami.middlewares=sso@file 115 | - traefik.http.routers.whoami.tls=true 116 | 117 | # ############################################################################# 118 | # Customized postgres 119 | # ############################################################################# 120 | 121 | postgres: 122 | container_name: ${COMPOSE_PROJECT_NAME}_postgres 123 | image: postgres:latest 124 | restart: unless-stopped 125 | env_file: 126 | - ./config/postgres.env 127 | ports: 128 | - target: 5432 129 | published: 5432 130 | mode: host 131 | 132 | networks: 133 | judo: 134 | aliases: 135 | - postgres.${DOMAIN} 136 | 137 | volumes: 138 | - ./.data/postgres/data:/var/lib/postgresql/data 139 | healthcheck: 140 | test: ["CMD-SHELL", "pg_isready -U judo"] 141 | interval: 10s 142 | timeout: 5s 143 | retries: 5 144 | 145 | # ############################################################################# 146 | # Keycloak 147 | # ############################################################################# 148 | 149 | keycloak: 150 | container_name: ${COMPOSE_PROJECT_NAME}_keycloak 151 | image: quay.io/keycloak/keycloak:12.0.4 152 | restart: unless-stopped 153 | 154 | env_file: 155 | - ./config/keycloak.env 156 | 157 | environment: 158 | - KEYCLOAK_FRONTEND_URL=https://keycloak.${DOMAIN}/auth 159 | - PROXY_ADDRESS_FORWARDING=true 160 | 161 | networks: 162 | judo: 163 | aliases: 164 | - keycloak.${DOMAIN} 165 | 166 | dns_search: ${DOMAIN} 167 | 168 | command: 169 | [ 170 | '-b', 171 | '0.0.0.0', 172 | '-Djboss.http.port=80', 173 | '-Djboss.https.port=443', 174 | '-Djboss.socket.binding.port-offset=0', 175 | '-Dkeycloak.migration.action=import', 176 | '-Dkeycloak.migration.provider=dir', 177 | '-Dkeycloak.migration.dir=/realm-config', 178 | '-Dkeycloak.migration.strategy=IGNORE_EXISTING', 179 | ] 180 | 181 | volumes: 182 | - ./cert/_.${DOMAIN}/cert.pem:/etc/x509/https/tls.crt 183 | - ./cert/_.${DOMAIN}/key.pem:/etc/x509/https/tls.key 184 | - ./config/keycloak-realm-config:/realm-config 185 | 186 | labels: 187 | - traefik.enable=true 188 | - traefik.backend=keycloak 189 | - traefik.docker.network=${COMPOSE_PROJECT_NAME}_judo 190 | - traefik.http.services.keycloak.loadBalancer.server.port=80 191 | 192 | # SSL configuration 193 | - traefik.http.routers.keycloak.entryPoints=https 194 | - traefik.http.routers.keycloak.rule=host(`keycloak.${DOMAIN}`) 195 | - traefik.http.routers.keycloak.tls=true 196 | 197 | healthcheck: 198 | test: ["CMD-SHELL", "curl -U --fail http://localhost:80/auth/realms/master"] 199 | interval: 10s 200 | timeout: 1s 201 | retries: 30 202 | 203 | depends_on: 204 | postgres: 205 | condition: service_healthy 206 | 207 | 208 | 209 | 210 | -------------------------------------------------------------------------------- /update-domain.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source ./.env 3 | 4 | new=${1:-example.com} 5 | from=$(echo "$DOMAIN" | sed 's/\./\\\./g') 6 | to=$(echo "$new" | sed 's/\./\\\./g') 7 | 8 | echo "FROM: $from" 9 | echo "TO: $to" 10 | 11 | find . -type f -not -path '*/\.git/*' \( -iname \*.yaml -o -iname \*.yml -o -iname \*.toml -o -iname \*.json -o -iname \*.env \) -exec sed -i '' -e "s/$from/$to/g" {} \; 12 | --------------------------------------------------------------------------------