JWKS#
В этом примере:
развертывается веб-приложение;
настраивается балансировка нагрузки с помощью
VirtualServer
;применяется политика
JWT
.
В отличие от примера с Установите ANIC. Добавьте в файл Здесь Создайте Secret с TLS-сертификатом и ключом для терминального TLS-шифрования в Примените настройки: Создайте Deployment и Service для веб-приложения: Примените настройки: Создайте Deployment и Service для Примените настройки: Создайте Примените настройки: Настройте Keycloak: Перейдите в Keycloak: Создайте новую область Перейдите во вкладку Перейдите во вкладку Сохраните секрет: Перейдите во вкладку Перейдите во вкладку Сохраните пароль: Создайте политику В приведенной ниже политике замените область Примените политику: Разверните ConfigMap с резолвером. Если в В этом примере мы создаем ConfigMap, используя стандартный DNS Kubernetes
Примечание При установке значения Примените конфигурацию: Настройте балансировку нагрузки. Создайте Примените настройки: Обратите внимание, что Получите токен клиента. Для доступа к веб-приложению клиент должен передавать токен-носитель.
Чтобы получить токен, выполните команду: Эта команда сохранит токен в переменной окружения TOKEN. Протестируйте конфигурацию. Попытка запроса без токена-носителя: Ответ: Запрос с корректным токеном-носителем: Ответ сервера:JWT
, здесь внешний провайдер идентификации (IdP) определен
с помощью поля JwksURI
. В качестве провайдера используется Keycloak
,
развернутый как контейнер и доступный с помощью ANIC.Предварительные действия#
/etc/hosts
записи:XXX.YYY.ZZZ.III webapp.example.com
XXX.YYY.ZZZ.III keycloak.example.com
webapp.example.com
— домен веб-приложения,
а keycloak.example.com
— домен Keycloak.Настройка JWKS#
Keycloak
:apiVersion: v1
kind: Secret
metadata:
name: tls-secret
type: kubernetes.io/tls
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFVENDQWZtZ0F3SUJBZ0lVS2hTQzBBcnhUblYrbjBhVnNENkFVTE5VQWhZd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0dERVdNQlFHQTFVRUF3d05LaTVsZUdGdGNHeGxMbU52YlRBZUZ3MHlNVEF4TVRZd01qSXpNekZhRncwegpNVEF4TVRRd01qSXpNekZhTUJneEZqQVVCZ05WQkFNTURTb3VaWGhoYlhCc1pTNWpiMjB3Z2dFaU1BMEdDU3FHClNJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURGeU1DSlhlSm9tMTdhcUVQc01NbTNlVzlpQzFHdlI4YW8KaDJhNmgvZWRXTUFndEtWSERmR2tPQ2V5NDBEdGtXTDN3U0NvZE1McnhPcnN2Lzhuc1VablFwQmNBekxBbzBJVgptYnhoS21WaS9EMkJpb2pBcDlqVXlsMjNma2RWMFdYM3NYV0JQekhSa3RyK0ozaW83YVcvNUl0WVBNWWFYM3dmCkZYRWFXVmQ4QmJDQ0hyVlZ3ckMvem9aTEF3dFE0d1I5NUI2NHdtd2d4TEhNZDlWZDRSZ1l2U0ppc1QzWi9IRkkKTGpaTGdMa0FlMGlDci9xdmFsdnVhU3BNVmJUd1lQZ2l6YWhXSVFTYjVyd29JeUhnYXFBWnRYSEhjNSsydDVoZQpMMDc2RjgrOE84b0hpdDR6WGpsR1V4TFNjTWFPTnI2ZHI0Q256NmlXZzJNTGlJcno0VnR4QWdNQkFBR2pVekJSCk1CMEdBMVVkRGdRV0JCUTdCSGpyZHlicnpWNHIwVkRrc2k3TXFPNWRKREFmQmdOVkhTTUVHREFXZ0JRN0JIanIKZHlicnpWNHIwVkRrc2k3TXFPNWRKREFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQTBHQ1NxR1NJYjNEUUVCQ3dVQQpBNElCQVFDdm5TdUY4dUFUWFl2VHVjVGhEcG9jKzI5RU1LVFp2VDBmSmJrNWZMaWQzYjhFTDQxdk5tTjRwUTUrCmJtSFh1bkhLL29aSm43bWVNTngwc0ZQMW1Pa1U5MXBqZVJLWmoxOXVNQjlvTVBreXdXRENuQ1BHYWtFUHpxOS8KWjFwcERKQ0FJc2cvME8wZ1BCMDdFSm9RcU0wdDlZc3BuMlJ4djMwUGdBZ3ZuSXduUlNzUWpvOEpxQ1VuemZJLwpPdXovNVl1UkhJRHQzY0RpdTdzWG1DTW01cFJ5eUd2WGZiWEsrSVFWOHZDRTZlZS9FTlNFcnB0NUdzeVNURjZKCk5LdDhXM1VwNkUvL2dwMkRvTXBxS0tGQkE0aG5OQXVzQVphTkNQdi9EY0xueG9xQUp4S0V5cmpxelJBeTlCRXkKRzBhSTJ5bitKWW5yVW8wMmc1OWFXalZMTzg4RwotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRREZ5TUNKWGVKb20xN2EKcUVQc01NbTNlVzlpQzFHdlI4YW9oMmE2aC9lZFdNQWd0S1ZIRGZHa09DZXk0MER0a1dMM3dTQ29kTUxyeE9ycwp2Lzhuc1VablFwQmNBekxBbzBJVm1ieGhLbVZpL0QyQmlvakFwOWpVeWwyM2ZrZFYwV1gzc1hXQlB6SFJrdHIrCkozaW83YVcvNUl0WVBNWWFYM3dmRlhFYVdWZDhCYkNDSHJWVndyQy96b1pMQXd0UTR3Ujk1QjY0d213Z3hMSE0KZDlWZDRSZ1l2U0ppc1QzWi9IRklMalpMZ0xrQWUwaUNyL3F2YWx2dWFTcE1WYlR3WVBnaXphaFdJUVNiNXJ3bwpJeUhnYXFBWnRYSEhjNSsydDVoZUwwNzZGOCs4TzhvSGl0NHpYamxHVXhMU2NNYU9OcjZkcjRDbno2aVdnMk1MCmlJcno0VnR4QWdNQkFBRUNnZ0VBQXhBcjR6VEFCK3k0R0Z6WXlIU3MreGwzWHlaYnVvSTdFbXNlYlM4ajU1enoKUk01bmJPVkxZOGEyM3E5a1Z3bVVaYy9vNkpMK1hkWnI2UVRFTitJbisvdHM3dS9odmxnSTh2cXhqek92NUV1Ugp6RXJQK1dQZ0dOT1ZoZnovcjlXUlpiZXE0VGlRVmZXWFRLNWgwUVAxT0RhYTdkL3JGWWQ3RGFRd1h6OFkrc080CnhqV0dNNFprOW1oWm1PbG9nZjNtYyszUFhYTWV6RFRMY2kzRWNpZVlaTkhTeXIzWkg2NU8rSkdsOFZ2bkZUWS8KQytQZi9tYmJKL282dlNWWDNWQUVIM29BY05qd1dqMkdBNUhiRk5RTnV0ckhRcnNkR0ZqUVB5aHNBYjNOV1h2bwo2M3hoS1NNbHpxSWd2WXZMbENOS0VjZmJsVjRuelJ4NVhhM0dzZjJkUFFLQmdRRDlYeEs4ekhpN2g4WjlQV2sxCktDZFlvZDFVa2ViWktYUVQvOUtNcmhrOE9abG1oV2hFK1lBY3lJRElVeFZuZ2xkR0d3RVViTFcyWEVnVStQVmEKM1ZlaUNCTlRWM3FwV3lYWXdIdG9yYm5WbGtlMGh4eE9WakhvSmpZWitmV0h6MDU0algvYkdsdWp5bVJGMWpoWApuMnhNUW5RUkV0S2FGN0R2d2FGK083dGExd0tCZ1FESDFndWRlVCsvQ3M1R3g3eEkwUnhwRUt4c0FtcUV3blBECklsaHoxZHJqbGZFaTRPZ25wK0ZOK05acGJiMHRaWmUyTTM2QXpMVENIUURmQVNJTlBDMkxzOHEvTjAyR2xzcG8KalVTd3M4cWc2N2ZjcG1UN1FVVTVMZmZuaDE3S1A5ZEdCdlRuK3Vza1MwVjRFZ2M0Ti9lS2pUQi9xcjYzYWRHUwp4dmRaYzdnNjl3S0JnRE9CQWdRUzVHL3FkN1M1cVFzL01GQmFCdTNNQXNzZUhCUjhxa1lpbGNxaVFzYU9VOVhCCmlnTlAxcTNpQmJYV3p2clhQbTd5Y2pXeHFJMXExaVUwWFQzNHVrVDB3V0J2d00vQXdOVlVpelFacWxYT0tUamIKV0tYQ0xyazFFRzRjKyt5Umh1MzQrNnZkMW1oRDFZd3FRZzkyYXJXVngrMis1eDYxazZoZmFBUmRBb0dCQU1Kcgp0QmM4VE5IQVlKb3FYenYwL3BBVm9icmZ5dVJwRHhsdFErTkd6OVFXSUduUHFPNVQvZmJQUDBPSmVjRStFeEU0CkhqNlBhdGxrUUdHMmgzdWE3YkQ2ZGluOVV4YTdoQ2VlTVpNOUNNbnhLNHVuODUwampvYW4rNFd0aFlKK0JDSmsKU0VlZUxzRzczZFdJcks5OGZBQzNodFRldVBoWElvZUx2a0N3UGpCWEFvR0JBUFBteVJJRGs5bUF5M2ZINnBtVwplRWlqYlBWbFdDd3FjalI5ZjQ0L3duVEpha0h4cVVxRk04cTVLNnJJejdPMmMvcDdmTm83andrVHc0R0hIVWcrCjQyVkpGOXRrdnRDbEhOZ3l6cXNjT3FjN0p2ZDNyYnBFbGVpNGgyTHo4Z0RDNFo4WldqWDBBKzVTaTlQd3RMaFEKN3pBZEJUMHk5WjZuNGYxMVg0UWhKSkR1Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
$ kubectl apply -f tls-secret.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
spec:
replicas: 1
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: webapp
image: angiesoftware/angie-hello:plain-text
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: webapp-svc
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: webapp
$ kubectl apply -f webapp.yaml
Keycloak
:apiVersion: v1
kind: Service
metadata:
name: keycloak
labels:
app: keycloak
spec:
ports:
- name: http
port: 8080
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: quay.io/keycloak/keycloak:20.0.1
args: ["start-dev"]
env:
- name: KEYCLOAK_ADMIN
value: "admin"
- name: KEYCLOAK_ADMIN_PASSWORD
value: "admin"
- name: KC_PROXY
value: "edge"
ports:
- name: http
containerPort: 8080
- name: https
containerPort: 8443
readinessProbe:
httpGet:
path: /realms/master
port: 8080
$ kubectl apply -f keycloak.yaml
VirtualServer
для Keycloak:apiVersion: k8s.angie.software/v1
kind: VirtualServer
metadata:
name: keycloak
spec:
host: keycloak.example.com
tls:
secret: tls-secret
redirect:
enable: true
upstreams:
- name: keycloak
service: keycloak
port: 8080
routes:
- path: /
action:
pass: keycloak
$ kubectl apply -f virtual-server-idp.yaml
https://keycloak.example.com
.Realm
с именем jwks-example
.Client
, создайте новый клиент с именем jwks-client
и
включите для него Client authentication
и Authorization
.Credentials
и скопируйте секрет клиента.export SECRET=<client secret>
Users
и создайте пользователя jwks-user
.Credentials
этого пользователя и установите пароль. Для примера подойдет любой пароль.export PASSWORD=<user password>
jwt-policy
с указанием JwksURI
и настройте поле JwksURI
так,
чтобы оно разрешало только те запросы к веб-приложению, которые содержат действительный JWT.jwks-example
на область (realm), созданную вами.
Значение spec.jwt.token
в примере установлено в $http_token
, так как токен клиента передается в заголовке HTTP.apiVersion: k8s.angie.software/v1
kind: Policy
metadata:
name: jwt-policy
spec:
jwt:
realm: MyProductAPI
token: $http_token
jwksURI: http://keycloak.default.svc.cluster.local:8080/realms/jwks-example/protocol/openid-connect/certs
$ kubectl apply -f jwks.yaml
jwksURI
используется имя хоста, необходимо настроить resolver
. Для этого необходимо добавить поле resolver-addresses
.kind: ConfigMap
apiVersion: v1
metadata:
name: angie-config
namespace: angie-ingress
data:
resolver-addresses: <resolver-address>
kube-dns.kube-system.svc.cluster.local
в качестве адреса резолвера.
Дополнительную информацию о resolver-addresses
и других связанных
ключах ConfigMap можно посмотреть в разделе ConfigMap.jwksURI
ответ может отличаться в зависимости от используемого IDP.
В некоторых случаях ответ может быть слишком большим для корректной обработки Angie.
Если это произойдет, необходимо настроить директиву subrequest_output_buffer_size
в контексте http
.
Это можно сделать с помощью http-snippets
. Пример обновленного ConfigMap
с subrequest_output_buffer_size
в контексте http
в angie.conf
приведен ниже.
Значение subrequest_output_buffer_size
указано только для примера
и должно быть изменено в соответствии с вашей средой.kind: ConfigMap
apiVersion: v1
metadata:
name: angie-config
namespace: angie-ingress
data:
resolver-addresses: kube-dns.kube-system.svc.cluster.local
http-snippets: |
subrequest_output_buffer_size 64k;
$ kubectl apply -f angie-config.yaml
VirtualServer
для веб-приложения:apiVersion: k8s.angie.software/v1
kind: VirtualServer
metadata:
name: webapp
spec:
host: webapp.example.com
policies:
- name: jwt-policy
upstreams:
- name: webapp
service: webapp-svc
port: 80
routes:
- path: /
action:
pass: webapp
$ kubectl apply -f virtual-server.yaml
VirtualServer
ссылается на политику jwt-policy
, созданную выше.$ export TOKEN=$(curl -k -L -X POST 'https://keycloak.example.com/realms/jwks-example/protocol/openid-connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode grant_type=password \
--data-urlencode scope=openid \
--data-urlencode client_id=jwks-client \
--data-urlencode client_secret=$SECRET \
--data-urlencode username=jwks-user \
--data-urlencode password=$PASSWORD \
| jq -r .access_token)
$ curl -H 'Accept: application/json' webapp.example.com
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>Angie/1.8.1</center>
</body>
</html>
$ curl -H 'Accept: application/json' -H "token: ${TOKEN}" webapp.example.com
Server address: 10.42.0.7:8080
Server name: webapp-5c6fdbcbf9-pt9tp
Date: 13/Dec/2022:14:50:33 +0000
URI: /
Request ID: f1241390ac51318afa4fcc39d2341359