Authorization for an application in the cloud
I started writing my pet project and got to the point where I needed to authorize the user in my application. Before this, I had experience using spring with keylock, but since I had now deployed the application in the cloud, I decided to find out how to use keylock in the cloud. Maybe you can use keylock as an authorization proxy?
To begin with, I had an application deployed in cuber and an ingress service. To begin with, I decided to use kubectl, not helm, in order to figure out what is responsible for what, and then switch to helm if necessary. Actually, I configured the ingress with this command.
kubectl --namespace default apply -f - <<< '
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress
spec:
rules:
- host: somehost.ru
http:
paths:
- path: /app
pathType: Prefix
backend:
service:
name: app
port:
number: 80
ingressClassName: nginx
'
After digging a little on the Internet, I found that ingress can delegate authorization using annotations, and the service to which you can delegate this way is called oauth2-proxy.
oauth2-proxy
oauth2-proxy itself does not know how to authorize users. It is essentially an adapter between ingress and services that can be used for authorization. IN documentation they have instructions on how to use Google, GitHub, GitLab for authorization… To begin with, I decided not to set up the checklock service, but to experiment with something ready-made, for example, authorize through GitHub.
In fact, following the instructions, I configured oauth2-proxy and added only two settings:
1) –set-xauthrequest – adds X-Auth-Request-User, X-Auth-Request-Groups, X-Auth-Request-Email and X-Auth-Request-Preferred-Username headers to the request.
2) –set-authorization-header – adds Authorization: Bearer…
kubectl --namespace default apply -f - <<< '
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: oauth2-proxy
name: oauth2-proxy
spec:
replicas: 1
selector:
matchLabels:
app: oauth2-proxy
template:
metadata:
labels:
app: oauth2-proxy
spec:
containers:
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:latest
imagePullPolicy: Always
ports:
- containerPort: 4180
protocol: TCP
args:
- --provider=github
- --email-domain=*
- --upstream=file:///dev/null
- --http-address=0.0.0.0:4180
- --scope=user:email
- --set-xauthrequest=true
- --set-authorization-header=true
env:
- name: OAUTH2_PROXY_CLIENT_ID
value: acb0636a63a40efab6d
- name: OAUTH2_PROXY_CLIENT_SECRET
value: 4076962096c9b3d1774e9cb6850b67eef23ad88e
- name: OAUTH2_PROXY_COOKIE_SECRET
value: 6Pf1nzU4nV2syd8e0JAkmw==
---
apiVersion: v1
kind: Service
metadata:
labels:
app: oauth2-proxy
name: oauth2-proxy
spec:
ports:
- name: http
port: 4180
protocol: TCP
targetPort: 4180
selector:
app: oauth2-proxy
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-oauth2-proxy
annotations:
nginx.ingress.kubernetes.io/proxy-buffer-size: "8k"
nginx.ingress.kubernetes.io/proxy-buffers-number: "4"
spec:
rules:
- host: somehost.ru
http:
paths:
- path: /oauth2
pathType: Prefix
backend:
service:
name: oauth2-proxy
port:
number: 4180
ingressClassName: nginx
'
Next you need to set the environment variables (in general, you can also pass them in arguments):
OAUTH2_PROXY_CLIENT_ID – taken from applications in git-hub
OAUTH2_PROXY_CLIENT_SECRET – also taken from the application, but shown only once, after which you need to create a new one
OAUTH2_PROXY_COOKIE_SECRET – can be generated like this python ‑c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())'
All that remains is to add annotations to the ingress service and authorization will work.
1) nginx.ingress.kubernetes.io/auth-url – judging by the instructions on the Internet, you can usually specify it via https://$host… but it didn’t work for me. There was a problem with the ssl certificate; perhaps it was not configured quite correctly. Therefore, I register this URL internally.
2 nginx.ingress.kubernetes.io/auth-signin: – this url will be returned to the user for unsuccessful authorization.
3) nginx.ingress.kubernetes.io/auth-response-headers: – here ingress adds the specified headers from the oauth2-proxy response to the request
4) nginx.ingress.kubernetes.io/proxy-buffer-size: – cache size, needed so that you don’t have to ask for authorization for every request.
kubectl --namespace default apply -f - <<< '
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress
annotations:
nginx.ingress.kubernetes.io/auth-url: "http://oauth2-proxy.default.svc.cluster.local:4180/oauth2/auth"
nginx.ingress.kubernetes.io/auth-signin: "https://$host/oauth2/start?rd=$escaped_request_uri"
nginx.ingress.kubernetes.io/auth-response-headers: "x-auth-request-user, x-auth-request-email, authorization"
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
spec:
rules:
- host: somehost.ru
http:
paths:
- path: /app
pathType: Prefix
backend:
service:
name: app
port:
number: 80
ingressClassName: nginx
'
We now go to https://somehost.ru/app and we are redirected to the authorization page in git-hub.
Keycloak
So, now I have some kind of authorization, but I want to control it myself. It’s time to switch oauth2-proxy to keylock. And at first I was a little mistaken. A lot of instructions on the Internet tell you how to set up keylock up to version 16. I set it up, looked at how to connect Russian providers like VK Yandex, etc. and realized that I needed to configure the 19th version. In general, pay attention to this, version 19 is better, lighter, faster and all that. At least that’s what the documentation says.
My actual keylock setup looks like this:
kubectl --namespace default apply -f - <<< '
apiVersion: v1
kind: Service
metadata:
name: keycloak
labels:
app: keycloak
spec:
type: LoadBalancer
ports:
- name: http
port: 80
targetPort: 8080
selector:
app: keycloak
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak
namespace: default
labels:
app: keycloak
spec:
replicas: 1
selector:
matchLabels:
app: keycloak
template:
metadata:
labels:
app: keycloak
spec:
containers:
- name: keycloak
image: playaru/keycloak-russian
args: [ "start" ]
env:
- name: KC_HOSTNAME_URL
value: "https://somehost.ru"
- name: KC_HOSTNAME_ADMIN_URL
value: "https://somehost.ru"
- name: KC_HOSTNAME_STRICT
value: "false"
- name: KC_HTTP_ENABLED
value: "true"
- name: KEYCLOAK_ADMIN
value: "keycloak"
- name: KEYCLOAK_ADMIN_PASSWORD
value: "keycloak"
- name: KC_PROXY
value: "edge"
- name: KC_HEALTH_ENABLED
value: "true"
- name: KC_DB
value: "postgres"
- name: KC_DB_URL
value: ""
- name: KC_DB_URL_DATABASE
value: "keycloak"
- name: KC_DB_USERNAME
value: ""
- name: KC_DB_PASSWORD
value: ""
ports:
- name: http
containerPort: 8080
readinessProbe:
httpGet:
path: /realms/master
port: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-test
namespace: default
spec:
tls:
- hosts:
- somehost.ru
secretName: k8s-secret
ingressClassName: nginx
rules:
- host: somehost.ru
http:
paths:
- path: /admin
pathType: Prefix
backend:
service:
name: keycloak
port:
number: 80
- path: /realms/
pathType: Prefix
backend:
service:
name: keycloak
port:
number: 80
- path: /resources/
pathType: Prefix
backend:
service:
name: keycloak
port:
number: 80
- path: /robots.txt
pathType: ImplementationSpecific
backend:
service:
name: keycloak
port:
number: 80
- path: /js/
pathType: Prefix
backend:
service:
name: keycloak
port:
number: 80
'
If you do not specify database settings, the keylock will create the database itself, but I needed to connect to my own. And the image I took playaru/keycloak-russian
so that I would immediately have Russian providers and not have to worry about connecting them.
In addition to the service settings, you need to pass the path to keycloak through the ingress, since now it will return the authorization page. It’s better not to forward the /admin path; for example, you can add a vpn to connect to services inside the cuber, but I haven’t learned how to do that yet.
Now you need to change the oauth2-proxy settings.
kubectl --namespace default apply -f - <<< '
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: oauth2-proxy
name: oauth2-proxy
spec:
replicas: 1
selector:
matchLabels:
app: oauth2-proxy
template:
metadata:
labels:
app: oauth2-proxy
spec:
containers:
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:latest
imagePullPolicy: Always
ports:
- containerPort: 4180
protocol: TCP
args:
- --provider=keycloak-oidc
- --upstream=file:///dev/null
- --http-address=0.0.0.0:4180
- --whitelist-domain=somehost.ru
- --cookie-domain=somehost.ru
- --set-xauthrequest=true
- --reverse-proxy=true
- --set-authorization-header=true
- --oidc-issuer-url=https://somehost.ru/realms/master
- --ssl-insecure-skip-verify=true
- --silence-ping-logging=true
- --email-domain=*
env:
- name: OAUTH2_PROXY_CLIENT_ID
value: ingress
- name: OAUTH2_PROXY_CLIENT_SECRET
value: <client-secret>
- name: OAUTH2_PROXY_COOKIE_SECRET
value: zTBum01F+fZmVntQutehXw==
---
apiVersion: v1
kind: Service
metadata:
labels:
app: oauth2-proxy
name: oauth2-proxy
spec:
ports:
- name: http
port: 4180
protocol: TCP
targetPort: 4180
selector:
app: oauth2-proxy
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-oauth2-proxy
annotations:
nginx.ingress.kubernetes.io/proxy-buffer-size: "8k"
nginx.ingress.kubernetes.io/proxy-buffers-number: "4"
spec:
rules:
- host: somehost.ru
http:
paths:
- path: /oauth2
pathType: Prefix
backend:
service:
name: oauth2-proxy
port:
number: 4180
ingressClassName: nginx
'
Here the following settings have been added:
1) –oidc-issuer-url – url by which the keylock will return settings for authorization, registration, etc.
2) –ssl-insecure-skip-verify – this is where I turn off certificate verification, because it was not possible to set up a successful trip inside the cuber. There is clearly something wrong with my certificate.
3) –silence-ping-logging – literally “do not write to the ping cuber logs”
How to set up a keylock and where to get client-secret is well described here.
Now, when we enter the page https://somehost.ru/app, authorization in the keylock will open.
conclusions
In general, I managed to do what I wanted, authorization works. But you still need to figure out the certificate and probably a lot more.
Well, as always cartalthough last time there was no point in subscribing to it, but I’m trying)