Пререквизиты
Так как работа сервисов СА во многом завязана на TLS-соединения, в инфраструктуре должна быть корректно настроена DNS-адресация.
Машины с установленными сервисами Keycloak, CA Gateway, AIA/CRL обязательно должны иметь корректные именования для прямого и обратного DNS-резолвинга
1. Установка KeyCloak
1.1 Подготовка базы данных
- установить PostgreSQL
- создать базу данных
- создать пользователя
CREATE DATABASE keycloak;
CREATE USER keycloak WITH PASSWORD `techpass`;
GRANT ALL PRIVILEGES ON DATABASE keycloak to keycloak;
- настроить PostgreSQL для подключения через TCP/IP (
pg_hba.conf)
1.2 Загрузить дистрибутив Keycloak
Загрузить дистрибутив TAR.GZ по ссылке https://www.keycloak.org/downloads
1.3 Создание группы и пользователя keycloak
# create group
groupadd keycloak
# create system account
useradd -g keycloak -r keycloak
1.4 Установка и настройка
- распаковать архив
tar -xvf keycloak-24.0.2.tar.gz
- переместить всё в
/optи выставить права
sudo mkdir /opt/keycloak
sudo mv keycloak-24.0.2 /opt/
sudo ln -s /opt/keycloak-24.0.2 /opt/keycloak
sudo chown -R keycloak:keycloak /opt/keycloak-24.0.2
sudo chown -R keycloak:keycloak /opt/keycloak
- выполнить настройки в файле конфигурации Keycloak - указать параметры подключения и БД и
hostname(должен быть указан верно!)
sudo nano /opt/keycloak/conf/keycloak.conf
- установить JVM 17+ (LTS)
sudo apt install -y openjdk-21-jre
- сборка и оптимизация
sudo -u keycloak /opt/keycloak/bin/kc.sh build --db=postgres
- пробный запуск без TLS (переменные окружения
KEYCLOAK_ADMINиKEYCLOAK_ADMIN_PASSWORDуказываются только при первом запуске)
export KEYCLOAK_ADMIN=admin
export KEYCLOAK_ADMIN_PASSWORD=admin
sudo -u keycloak /opt/keycloak/bin/kc.sh start-dev
- проверка, что сервис запустился
В браузере открываем http://[адрес]:8080/
2. Запуск Discovery-сервиса
Первым из состава СА запускается Discovery-сервис, так как он будет агрегировать информацию о запущенных экземплярах.
- создаем пользователя для работы сервисов СА
# create group
groupadd causer
# create system account
useradd -g causer -r causer
- копируем файлы сервиса в
/opt
sudo mkdir -p /opt/st-ca/ca-eureka
sudo cp ca-eureka-1.0.0.jar /opt/st-ca/ca-eureka/
sudo chown -R causer:causer /opt/st-ca/ca-eureka
- создаём сервис-файл
sudo nano /etc/systemd/system/ca-eureka.service
- описываем сервис
[Unit]
Description=ca-eureka
After=network.target
[Service]
WorkingDirectory=/opt/st-ca/ca-eureka
ExecStart=/opt/st-ca/jar_start_wrapper.sh /opt/st-ca/ca-eureka
User=causer
Type=simple
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
Приведённый сервис-файл работает через скрипт-обёртку, которая позволяет запускать любой JAR-файл в целевом каталоге.
Это удобно, когда JAR-файл содержит в названии версию компонента, чтобы не изменять сервис-файл каждый раз при обновлении версии.
- создаём скрипт-обертку
sudo nano /opt/st-ca/jar_start_wrapper.sh
#!/bin/bash
if [ "$#" -ne 1 ]; then
echo "Illegal number of parameters"
exit 1
fi
java -Xms128m -Xmx512m -jar $1/*.jar
sudo chown causer:causer /opt/st-ca/jar_start_wrapper.sh
- запускаем Discovery-сервис
sudo systemctl start ca-eureka.service
- проверяем по логу, что сервис запустился успешно
journalctl -u ca-eureka.service -f -o cat -n 500
3. Установка CA Core
3.1 Подготовка базы данных
- установить PostgreSQL
- создать базу данных
- создать пользователя
CREATE DATABASE ca_core;
CREATE USER ca_core WITH PASSWORD `techpass`;
GRANT ALL PRIVILEGES ON DATABASE ca_core to ca_core;
3.2 Подготовка сервиса
- при необходимости создаём пользователя (см. п. 2) в ОС
- копируем файлы сервиса в
/opt/st-ca/ca-core, присваиваем нужные права черезchown - создаём сервис-файл
sudo nano /etc/systemd/system/ca-core.service
- описываем сервис
[Unit]
Description=ca-core
After=network.target ca-eureka
[Service]
WorkingDirectory=/opt/st-ca/ca-core
ExecStart=/opt/st-ca/jar_start_wrapper.sh /opt/st-ca/ca-core
User=causer
Type=simple
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
- при необходимости создаём скрипт-обёртку (см. п. 2)
3.3 Первоначальная конфигурация
- генерация ключей УЦ (
gen_root_p12.sh)
DAYS='3650'
SUBJECT='/CN=ST CA Local Stand Root/O=SafeTech Ltd./OU=IT/L=Moscow/ST=Moscow/C=RU'
openssl req \
-x509 \
-newkey rsa:4096 \
-addext basicConstraints=critical,CA:TRUE,pathlen:3 \
-out cacert.pem \
-keyout privkey.pem \
-days "${DAYS}" \
-subj "${SUBJECT}" \
-nodes
# !!! -name "st-ca-root" here is an alias, must be provided in application.yml
openssl pkcs12 -export -out root.p12 -inkey privkey.pem -in cacert.pem -name "st-ca-root"
# move keys to ca-core dir
mkdir -p /opt/st-ca/ca-core/key
mv root.p12 /opt/st-ca/ca-core/key
mv cacert.pem /opt/st-ca/ca-core/key
mv privkey.pem /opt/st-ca/ca-core/key
- заполняем файл конфигурации для первого запуска БЕЗ управления пользователями
- корректно заполняем
datasource - указываем, где будем в будущем распространять корневой сертификат и список отзыва в секции
certificates - указываем адрес Discovery-сервиса в секции
eureka
- корректно заполняем
sudo nano /opt/st-ca/ca-core/application.yml
server:
port: 9000
spring:
jpa:
# -- jpa.database-platform
# Dialects can be found here https://docs.jboss.org/hibernate/orm/6.0/javadocs/org/hibernate/dialect/package-summary.html
# For MS SQL Servers
# use exact dialect version, even if it's marked as Deprecated, for example org.hibernate.dialect.SQLServer2012Dialect
# if you have error with SSL cert - use trustServerCertificate=true option in connection-url
database-platform: org.hibernate.dialect.PostgreSQLDialect
# database-platform: org.hibernate.dialect.OracleDialect
# database-platform: org.hibernate.dialect.SQLServer2012Dialect
datasource:
url: jdbc:postgresql://localhost/ca_core
username: ca_core
password: techpass
ca:
security:
enabled: false
root:
crl-validity: 24 #hours
max-suspension-period: 1 #months
rsa:
key-store-file: key/root.p12
key-alias: st-ca-root
key-pass: techpass
certificates:
validity-days: 365
crl-dist-points: [http://o-ca-stand.loc/st-ca/root-ca.crl]
ocsp-responder: http://o-ca-stand.loc/st-ca/ocsp
ca-cert-dist-point: http://o-ca-stand.loc/st-ca/root.crt
eureka:
client:
serviceUrl:
defaultZone: http://o-ca-stand.loc:8761/eureka/
instance:
preferIpAddress: false
- запускаем сервис
sudo systemctl start ca-core.service
- проверяем по логу, что сервис запустился успешно
journalctl -u ca-core.service -f -o cat -n 500
4. Выпуск сертификатов для веб-сервисов
4.2 Создание шаблона в CA Core для выпуска сертификатов веб-сервисов
- формируем запрос на создание шаблона на CA
- POST
/api/v1/templates - тело - запрос на создание шаблона по API
- POST
{
"name": "Web Server",
"description": "This template is used to issue certificates for web servers",
"maxValidityPeriod": 365,
"minKeySize": 2048,
"keyUsages": 160,
"msSubjectNameFlags": 1,
"msEnrollmentFlags": 1,
"msGeneralFlags": 65,
"msPrivateKeyFlags": 1,
"certificateAttributes": [
{
"oid": "1.3.6.1.4.1.311.20.2",
"name": "Certificate Template Name",
"oidReferenceId": 2,
"critical": "true"
},
{
"oid": "2.5.29.15",
"name": "Key Usage",
"oidReferenceId": 4,
"critical": "true"
},
{
"oid": "2.5.29.37",
"name": "Enhanced Key Usage",
"oidReferenceId": 10,
"critical": "true"
}
],
"accessControlList": [
{
"role": "CaAdministrator",
"permission": 15
}
]
}
- формируем запрос на привязку EKU к шаблону: Проверка подлинности сервера (1.3.6.1.5.5.7.3.1), Проверка подлинности клиента (1.3.6.1.5.5.7.3.2)
- POST
/api/v1/templates/{template-id}/eku - тело - запрос на создание шаблона по API
- POST
[
"1.3.6.1.5.5.7.3.1",
"1.3.6.1.5.5.7.3.2"
]
4.2 Файл с кастомными OID для OpenSSL
nano custom-oids.conf
oid_section = OIDs
[ OIDs ]
CertificateTemplateName=1.3.6.1.4.1.311.20.2
CertificateTemplateOID=1.3.6.1.4.1.311.21.7
4.3 Cертификат для Keycloak
- Формируем запрос на сертификат
SUBJECT='/CN=o-ca-stand.loc/O=SafeTech Ltd./OU=IT/L=Moscow/ST=Moscow/C=RU'
REQ_FILE=keycloak-req.pem
KEY_FILE=keycloak-privkey.pem
openssl req \
-new -nodes \
-config custom-oids.conf \
-newkey rsa:4096 \
-subj "${SUBJECT}" \
-addext "basicConstraints = critical,CA:FALSE" \
-addext "keyUsage = digitalSignature, keyEncipherment" \
-addext "extendedKeyUsage = serverAuth, clientAuth" \
-addext "subjectAltName = DNS:o-ca-stand.loc" \
-addext "CertificateTemplateName = ASN1:UTF8String:Web Server" \
-out ${REQ_FILE} \
-keyout ${KEY_FILE}
echo "Certificate request:"
cat $REQ_FILE
echo ""
echo "Certificate request without new lines and headers:"
cat ./$REQ_FILE | tr --delete '\n' | sed -r 's/-----BEGIN CERTIFICATE REQUEST-----//' | sed -r 's/-----END CERTIFICATE REQUEST-----//'
echo ""
- Запрашиваем сертификат у CA Core
- POST
/certs/issue - тело - запрос на выпуск сертификата
- POST
{
"type": "PKCS10",
"request_content": "MIIFNDCCAxwCAQAwbTEXMBUGA1UE...3rAKaPPW/bJJQxiFZ6iH0BmOkq59DH2gZg/naw=="
}
результат запроса (сертификат) сохраняем в файл
keycloak-cert.pemСобираем в одной папке
sudo mkdir -p /opt/keycloak/conf/tsl
# Copy our key and cert
sudo mv keycloak-privkey.pem /opt/keycloak/conf/tls/
sudo mv keycloak-cert.pem /opt/keycloak/conf/tsl/
# Root cert to build CA chain
sudo cp /opt/st-ca/ca-core/key/cacert.pem /opt/keycloak/conf/tsl/
sudo chown -R keycloak:keycloak /opt/keycloak/conf/tsl/
копируем содержание
cacert.pemвkeycloak-cert.pemв НАЧАЛО файла, чтобы в файлеkeycloak-cert.pemбыло два сертификата (цепочка)в файле
nano /opt/keycloak/conf/keycloak.confуказваем
https-certificate-file=/opt/keycloak/conf/tsl/keycloak-cert.pem
https-certificate-key-file=/opt/keycloak/conf/tsl/keycloak-privkey.pem
- запускаем Keycloak
sudo -u keycloak /opt/keycloak/bin/kc.sh start --optimized
- проверяем, что сертификат подцепился в браузере
https://[address]:8443/
4.3 Cертификат для CA Gateway
- Формируем запрос на сертификат
SUBJECT='/CN=o-ca-stand.loc/O=SafeTech Ltd./OU=IT/L=Moscow/ST=Moscow/C=RU'
REQ_FILE=gateway-req.pem
KEY_FILE=gateway-privkey.pem
openssl req \
-new -nodes \
-config custom-oids.conf \
-newkey rsa:4096 \
-subj "${SUBJECT}" \
-addext "basicConstraints = critical,CA:FALSE" \
-addext "keyUsage = digitalSignature, keyEncipherment" \
-addext "extendedKeyUsage = serverAuth, clientAuth" \
-addext "subjectAltName = DNS:o-ca-stand.loc" \
-addext "CertificateTemplateName = ASN1:UTF8String:Web Server" \
-out ${REQ_FILE} \
-keyout ${KEY_FILE}
echo "Certificate request:"
cat $REQ_FILE
echo ""
echo "Certificate request without new lines and headers:"
cat ./$REQ_FILE | tr --delete '\n' | sed -r 's/-----BEGIN CERTIFICATE REQUEST-----//' | sed -r 's/-----END CERTIFICATE REQUEST-----//'
echo ""
- Запрашиваем сертификат у CA
- POST
/certs/issue - тело - запрос на выпуск сертификата
- POST
{
"type": "PKCS10",
"request_content": "MIIFNDCCAxwCAQAwbTEXMBUGA1UE...3rAKaPPW/bJJQxiFZ6iH0BmOkq59DH2gZg/naw=="
}
- результат запроса (сертификат) сохраняем в файл gateway-cert.pem
- Собираем в одной папке и делаем p12
# Check our key and cert
cat gateway-privkey.pem
cat gateway-cert.pem
# Root cert to build CA chain
cp /opt/st-ca/ca-core/key/cacert.pem ./
# !!! -name "st-ca-gateway" is an alias, will be used in application.yml
openssl pkcs12 -export -out gateway.p12 -inkey gateway-privkey.pem -in gateway-cert.pem -certfile cacert.pem -name "st-ca-gateway"
4.4 Сертификат для Web GUI
Если Web GUI будет развернут на доменном имени, отличном от CA Gateway, тогда по аналогии сформировать сертификат для Web GUI
5. Конфигурация Keycloak (базовая)
5.1 Сервис-файл
sudo nano /etc/systemd/keycloak.service
[Unit]
Description=Keycloak Server
After=network.target postgresql.service
[Service]
User=keycloak
Group=keycloak
SuccessExitStatus=0 143
ExecStart=/opt/keycloak/bin/kc.sh start --optimized
[Install]
WantedBy=multi-user.target
sudo systemctl start keycloak
Проверка логов
journalctl -u keycloak.service -f -o cat -n 500
5.2 Настройка пользователей и ролей
- открываем страницу Администрирования
https://[адрес]:8080/ - создаём отдельный realm для CA (например,
st-ca) при необходимости (если не создаём, то используем realm master) создаём роли в новом realm
- CaAdministrator
- CaAuditor
- CaExternal
- CaOperator
создаём ПОЛЬЗОВАТЕЛЯ в Keycloak (пользователь, работающий без домена)
ca-standalone-admin, обязательно поставить First Name, Last Name, email (любые)- задать пароль, убрать Temporary
- установить роль
CaAdministrator
создаём ПОЛЬЗОВАТЕЛЯ в Keycloak (от имени которого CA Core сможет запрашивать роли)
ca-core, обязательно поставить First Name, Last Name, email (любые)- задать пароль, убрать Temporary
- установить роль
CaAdministrator - установить роль
realm-management view-clients
создаём КЛИЕНТА в Keycloak (для OpenID Connect)
ca-gateway, обязательно поставить First Name, Last Name, email (любые)- valid redirect URIs: URL компонента
gateway(например,https://o-ca-stand.loc:9001/*)
создаём КЛИЕНТА в Keycloak (для OpenID Connect)
ca-ui, обязательно поставить First Name, Last Name, email (любые)- valid redirect URIs: URL компонента
Web UI(например,https://o-ca-stand.loc/*)
6. Конфигурация CA Core
После настройки Keycloak мы можем настроить CA Core для работы с функциями разграничения доступа
Для этого модифицируем файл application.yml
sudo nano /opt/st-ca/ca-core/application.yml
- добавляем параметр
issuer-uri, указывая полный адрес REALM в Keycloak - включаем
ca.security.enable: true - заполняем секцию
ca.sso- указываем данные КЛИЕНТА Keycloak и его секрет (копируем из Keycloak, вкладка
Credentialsв настройках КЛИЕНТА) - указываем данные пользователя для получения ролей
- указываем REALM
- указываем данные КЛИЕНТА Keycloak и его секрет (копируем из Keycloak, вкладка
server:
port: 9000
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://o-ca-stand.loc:8443/realms/st-ca
jpa:
# -- jpa.database-platform
# Dialects can be found here https://docs.jboss.org/hibernate/orm/6.0/javadocs/org/hibernate/dialect/package-summary.html # For MS SQL Servers
# use exact dialect version, even if it's marked as Deprecated, for example org.hibernate.dialect.SQLServer2012Diale> # if you have error with SSL cert - use trustServerCertificate=true option in connection-url
database-platform: org.hibernate.dialect.PostgreSQLDialect
open-in-view: off
# database-platform: org.hibernate.dialect.OracleDialect
# database-platform: org.hibernate.dialect.SQLServer2012Dialect
datasource:
url: jdbc:postgresql://localhost/ca_core
username: ca_core
password: techpass
hikari:
connection-timeout: 20000
maximum-pool-size: 15
ca:
impl:
rsa
security:
enabled: true
root:
crl-validity: 24 #hours
max-suspension-period: 1 #months
rsa:
key-store-file: key/root.p12
key-alias: pc-ca-root
key-pass: techpass
certificates:
validity-days: 365
crl-dist-points: [http://o-ca-stand.loc/st-ca/root-ca.crl]
ocsp-responder: http://o-ca-stand.loc/st-ca/ocsp
ca-cert-dist-point: http://o-ca-stand.loc/st-ca/root.crt
sso:
# tech client
client-id: ca-gateway
client-secret: HqqUHd5y9QZnMAktou7uSc7axvGm5GCY
# tech user to fetch roles
username: ca-core
password: techpass
# realm settings
realm: st-ca
eureka:
client:
serviceUrl:
defaultZone: http://o-ca-stand.loc:8761/eureka/
instance:
preferIpAddress: false
logging:
level:
root: info
tech.paycon: debug
7. Установка CA Gateway
- при необходимости создаём пользователя (см. п. 2) в ОС
- копируем файлы сервиса в
/opt/st-ca/ca-gateway, присваиваем нужные права черезchown - создаём сервис-файл
sudo nano /etc/systemd/system/ca-gateway.service
- описываем сервис
[Unit]
Description=ca-gateway
After=network.target ca-eureka
[Service]
WorkingDirectory=/opt/st-ca/ca-gateway
ExecStart=/opt/st-ca/jar_start_wrapper.sh /opt/st-ca/ca-gateway
User=causer
Type=simple
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
при необходимости создаём скрипт-обёртку (см. п. 2)
копируем p12-файл, созданный на шаге 4 для Gateway, в
/opt/st-ca/ca-gateway/key/gateway.p12создаём конфигурацию
- указываем путь к файлу с ключами, алиас и пароль в секции
server.ssl - заполняем секцию
spring.security, указывая параметры REALM и КЛИЕНТА (см. п. 5)
- указываем путь к файлу с ключами, алиас и пароль в секции
sudo nano /opt/st-ca/ca-gateway/application.yml
server:
port: 9001
ssl:
key-store: key/gateway.p12
key-alias: st-ca-gateway
key-store-password: techpass
routes:
refresh:
delay: 5000 # Период обновления роутов в миллисекундах
spring:
cloud:
gateway:
x-forwarded:
enabled: true
security:
oauth2:
client:
registration:
my-oidc-client:
provider: keycloak
client-id: ca-gateway
client-secret: HqqUHd5y9QZnMAktou7uSc7axvGm5GCY
authorization-grant-type: authorization_code
scope: openid,profile
provider:
keycloak:
issuer-uri: https://o-ca-stand.loc:8443/realms/pc-ca
eureka:
client:
serviceUrl:
defaultZone: http://o-ca-stand.loc:8761/eureka/
instance:
preferIpAddress: false
logging:
level:
root: info
tech.paycon: debug
- запускаем сервис
sudo systemctl start ca-gateway.service
- проверяем по логу, что сервис запустился успешно
journalctl -u ca-gateway.service -f -o cat -n 500
8. Установка веб-сервера для AIA/CRL DP/Web UI
В качестве веб-сервера может использоваться любой веб-сервер (например, Apache2 или nginx).
Ниже приведена инструкция для Apache2, которая может быть адаптирована для другого веб-сервера.
8.1 Установка веб-сервера
sudo apt install apache2
8.2 Конфигурация веб-сервера
- копируем pem-файлы ключей, сформированные на шаге 4.4, в каталог с ключами.
sudo mkdir /etc/apache2/ssl
sudo cp /opt/keycloak/conf/tsl/keycloak-privkey.pem /etc/apache2/ssl/web-privkey.pem
sudo cp /opt/keycloak/conf/tsl/keycloak-cert.pem /etc/apache2/ssl/web-cert.pem
- Включаем TLS в Apache2
sudo nano /etc/apache2/sites-available/default-ssl.conf
Указываем пути к файлам ключей и сертификата
SSLCertificateFile /etc/apache2/ssl/web-cert.pem
SSLCertificateKeyFile /etc/apache2/ssl/web-privkey.pem
Включаем модуль TLS и конфигурацию
sudo a2enmod ssl
sudo a2ensite default-ssl.conf
sudo systemctl restart
8.3 AIA / CRL DP
Чтобы проверить, что всё настроено правильно и работает, данные запросы лучше выполнить вручную
1. Запрос на получение токена доступа к CA Core
POST https://[keycloak-url]:[port]/realms/[realm]/protocol/openid-connect/token
x-www-form-url-encoded
client_id: [ID КЛИЕНТА, созданного в п. 5.2]
client_secret: [Секрет КЛИЕНТА, созданного в п. 5.2]
username: [Логин ПОЛЬЗОВАТЕЛЯ, созданного в п. 5.2]
password: [Пароль ПОЛЬЗОВАТЕЛЯ, созданного в п. 5.2]
password: [Пароль ПОЛЬЗОВАТЕЛЯ, созданного в п. 5.2]
grant_type: password
Например,
curl --location 'https://o-ca-stand.loc:8443/realms/pc-ca/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=ca-gateway' \
--data-urlencode 'client_secret=HqqUHd5y9QZnMAktou7uSc7axvGm5GCY' \
--data-urlencode 'username=ca-standalone-admin' \
--data-urlencode 'password=techpass' \
--data-urlencode 'grant_type=password'
В ответ будет получен JSON, в котором будет присутствовать access_token
Например,
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJSOUE1VENTRFZMMjhabnd5UnpHNU1kSHp4OHliVFRqc1FoVFNXTGhqODRzIn0.eyJleHAiOjE3MTMyMTA0MzAsImlhdCI6MTcxMzE3NDQzMCwianRpIjoiMGM2NzdiZGQtZTc3Ni00ZTU2LTk5YWEtMzZmNjhmMjdmOWJhIiwiaXNzIjoiaHR0cHM6Ly9vLWNhLXN0YW5kLmxvYzo4NDQzL3JlYWxtcy9wYy1jYSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiIzYzkyMjE4Mi1kNTI4LTQ2MTEtYmI2ZC0zYWE1N2M0MmZmODkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJjYS1nYXRld2F5Iiwic2Vzc2lvbl9zdGF0ZSI6IjA1MjgzMjJjLTU4ZDQtNDFlOC04OWUzLWI1Y2MxMGYxYTY0YiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsImRlZmF1bHQtcm9sZXMtcGMtY2EiLCJDYUFkbWluaXN0cmF0b3IiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsInNpZCI6IjA1MjgzMjJjLTU4ZDQtNDFlOC04OWUzLWI1Y2MxMGYxYTY0YiIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiTG9uZWx5IEFkbWluIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiY2Etc3RhbmRhbG9uZS1hZG1pbiIsImdpdmVuX25hbWUiOiJMb25lbHkiLCJmYW1pbHlfbmFtZSI6IkFkbWluIiwiZW1haWwiOiJsb25lbHktYWRtaW5Ac2FmZS10ZWNoLnJ1In0.iA65yS2zY09_Qa4j1QFrNispIe-75Hebl9P1fkQpuZ4CRFocMJnRIyw0WFxI1z1VdbdjjLf9PcN7W5hQNUieQfPPoBFohUx81KXKHWtMXX1Vi75aIjhLz2Bw-E0j1oeo26gkUJreQH24FktGK7ilzxLM8uSdJ0Xjqd6RndvMCfgoCyFQfFwywWQn0_mE7awLLVJpxijs-N-4tM_E7i60vHV0wLQvPxHIEI9wGDgOuytwIYsomc9fzylwpvGVbiX8Cy4RmXR7p0jvAflxZbCr4O855QG9i28L9EQ0kowUq_jMlk5ffa4_RaNoIbkKP8hZoktMSnroO63XAT8vCXqDew",
"expires_in": 36000,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhYjE4NWRkZC02NzQ2LTRmYjItYWMwZi03MzE4ZjIxZDIxZjAifQ.eyJleHAiOjE3MTMxNzYyMzAsImlhdCI6MTcxMzE3NDQzMCwianRpIjoiMGMwMzQ0MTYtNmJhZi00YmFkLTkyZjgtNGNkNzg5NzZjYjg5IiwiaXNzIjoiaHR0cHM6Ly9vLWNhLXN0YW5kLmxvYzo4NDQzL3JlYWxtcy9wYy1jYSIsImF1ZCI6Imh0dHBzOi8vby1jYS1zdGFuZC5sb2M6ODQ0My9yZWFsbXMvcGMtY2EiLCJzdWIiOiIzYzkyMjE4Mi1kNTI4LTQ2MTEtYmI2ZC0zYWE1N2M0MmZmODkiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiY2EtZ2F0ZXdheSIsInNlc3Npb25fc3RhdGUiOiIwNTI4MzIyYy01OGQ0LTQxZTgtODllMy1iNWNjMTBmMWE2NGIiLCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiIwNTI4MzIyYy01OGQ0LTQxZTgtODllMy1iNWNjMTBmMWE2NGIifQ.3H-jShNbt-g-YUxxTiEJzADcX0_osIDyWd3onHuje5Y3Fscv5tq1c5-Q8m_7DO1n-1qS2a7mKJiajgxuXyi-AA",
"token_type": "Bearer",
"not-before-policy": 0,
"session_state": "0528322c-58d4-41e8-89e3-b5cc10f1a64b",
"scope": "email profile"
}
2. Запрос на получение корневого сертификата
GET https://[ca-gateway]:[port]/ca-core/root/info
Заголовок Authorization должен иметь значение Bearer [access_token]
Например,
curl --location 'http://o-ca-stand.loc:9001/ca-core/api/v1/root/info' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJSOUE1VENTRFZMMjhabnd5UnpHNU1kSHp4OHliVFRqc1FoVFNXTGhqODRzIn0.eyJleHAiOjE3MTMyMTA0MzAsImlhdCI6MTcxMzE3NDQzMCwianRpIjoiMGM2NzdiZGQtZTc3Ni00ZTU2LTk5YWEtMzZmNjhmMjdmOWJhIiwiaXNzIjoiaHR0cHM6Ly9vLWNhLXN0YW5kLmxvYzo4NDQzL3JlYWxtcy9wYy1jYSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiIzYzkyMjE4Mi1kNTI4LTQ2MTEtYmI2ZC0zYWE1N2M0MmZmODkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJjYS1nYXRld2F5Iiwic2Vzc2lvbl9zdGF0ZSI6IjA1MjgzMjJjLTU4ZDQtNDFlOC04OWUzLWI1Y2MxMGYxYTY0YiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsImRlZmF1bHQtcm9sZXMtcGMtY2EiLCJDYUFkbWluaXN0cmF0b3IiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsInNpZCI6IjA1MjgzMjJjLTU4ZDQtNDFlOC04OWUzLWI1Y2MxMGYxYTY0YiIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiTG9uZWx5IEFkbWluIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiY2Etc3RhbmRhbG9uZS1hZG1pbiIsImdpdmVuX25hbWUiOiJMb25lbHkiLCJmYW1pbHlfbmFtZSI6IkFkbWluIiwiZW1haWwiOiJsb25lbHktYWRtaW5Ac2FmZS10ZWNoLnJ1In0.iA65yS2zY09_Qa4j1QFrNispIe-75Hebl9P1fkQpuZ4CRFocMJnRIyw0WFxI1z1VdbdjjLf9PcN7W5hQNUieQfPPoBFohUx81KXKHWtMXX1Vi75aIjhLz2Bw-E0j1oeo26gkUJreQH24FktGK7ilzxLM8uSdJ0Xjqd6RndvMCfgoCyFQfFwywWQn0_mE7awLLVJpxijs-N-4tM_E7i60vHV0wLQvPxHIEI9wGDgOuytwIYsomc9fzylwpvGVbiX8Cy4RmXR7p0jvAflxZbCr4O855QG9i28L9EQ0kowUq_jMlk5ffa4_RaNoIbkKP8hZoktMSnroO63XAT8vCXqDew'
Из ответа берём значение payload.pem, сохраняем в файл, указанный в поле certificates на шаге 3.3
Например, /var/www/html/st-ca/root.crt
3. Публикация и получение актуального CRL
GET https://[ca-gateway]:[port]/ca-core/root/crl
Заголовок Authorization должен иметь значение Bearer [access_token]
Например,
curl --location 'http://o-ca-stand.loc:9001/ca-core/api/v1/root/info' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJSOUE1VENTRFZMMjhabnd5UnpHNU1kSHp4OHliVFRqc1FoVFNXTGhqODRzIn0.eyJleHAiOjE3MTMyMTA0MzAsImlhdCI6MTcxMzE3NDQzMCwianRpIjoiMGM2NzdiZGQtZTc3Ni00ZTU2LTk5YWEtMzZmNjhmMjdmOWJhIiwiaXNzIjoiaHR0cHM6Ly9vLWNhLXN0YW5kLmxvYzo4NDQzL3JlYWxtcy9wYy1jYSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiIzYzkyMjE4Mi1kNTI4LTQ2MTEtYmI2ZC0zYWE1N2M0MmZmODkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJjYS1nYXRld2F5Iiwic2Vzc2lvbl9zdGF0ZSI6IjA1MjgzMjJjLTU4ZDQtNDFlOC04OWUzLWI1Y2MxMGYxYTY0YiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsImRlZmF1bHQtcm9sZXMtcGMtY2EiLCJDYUFkbWluaXN0cmF0b3IiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsInNpZCI6IjA1MjgzMjJjLTU4ZDQtNDFlOC04OWUzLWI1Y2MxMGYxYTY0YiIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiTG9uZWx5IEFkbWluIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiY2Etc3RhbmRhbG9uZS1hZG1pbiIsImdpdmVuX25hbWUiOiJMb25lbHkiLCJmYW1pbHlfbmFtZSI6IkFkbWluIiwiZW1haWwiOiJsb25lbHktYWRtaW5Ac2FmZS10ZWNoLnJ1In0.iA65yS2zY09_Qa4j1QFrNispIe-75Hebl9P1fkQpuZ4CRFocMJnRIyw0WFxI1z1VdbdjjLf9PcN7W5hQNUieQfPPoBFohUx81KXKHWtMXX1Vi75aIjhLz2Bw-E0j1oeo26gkUJreQH24FktGK7ilzxLM8uSdJ0Xjqd6RndvMCfgoCyFQfFwywWQn0_mE7awLLVJpxijs-N-4tM_E7i60vHV0wLQvPxHIEI9wGDgOuytwIYsomc9fzylwpvGVbiX8Cy4RmXR7p0jvAflxZbCr4O855QG9i28L9EQ0kowUq_jMlk5ffa4_RaNoIbkKP8hZoktMSnroO63XAT8vCXqDew'
Из ответа берём значение payload.content, форматируем в PEM, сохраняем в файл, указанный в поле certificates на шаге 3.3
Например, /var/www/html/st-ca/root-ca.crl
8.3.1 Автоматизация публикации CRL
Публикацию CRL можно автоматизировать shell-скриптом или любым другим инструментом.
После чего поместить этот скрипт в cron-задачу.
Например, php-скрипт ниже выполняет задачу публикации CRL.
Его помещение в cron для выполнения один раз в 12 часов решает задачу актуальности CRL.
<?php
// ---- Access Token
$KC_GET_TOKEN_URL = 'https://o-ca-stand.loc:8443/realms/pc-ca/protocol/openid-connect/token';
$KC_CLIENT_ID = 'ca-gateway';
$KC_CLIENT_SECRET = '[secret]';
$USERNAME = 'ca-standalone-admin';
$PASSWORD = '[password]';
// ---- CA Core
$ca_api_url = 'http://o-ca-stand.loc:9000/api/v1';
// ---- Publish point
$publish_location = '/var/www/html/pc-ca/root-ca.crl';
// ---------------------------------
// Get access token
$request = "client_id=$KC_CLIENT_ID";
$request .= "&client_secret=$KC_CLIENT_SECRET";
$request .= "&username=$USERNAME";
$request .= "&password=$PASSWORD";
$request .= "&grant_type=password";
$ch = curl_init($KC_GET_TOKEN_URL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/x-www-form-urlencoded',
'Content-Length: ' . strlen($request))
);
// execute request
$output = curl_exec($ch);
$error_description = curl_error($ch);
curl_close($ch);
// check result
if (false === $output) {
die("Error getting access token from Keycloak: $error_description");
}
$access_token = json_decode($output, true)['access_token'];
print "Access Token: \n$access_token\n\n";
// ---------------------------------
// Publish actual CRL
$ch = curl_init($ca_api_url . '/root/crl');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Authorization: Bearer ' .$access_token
));
// execute request
$output = curl_exec($ch);
$error_description = curl_error($ch);
curl_close($ch);
// check result
if (false === $output) {
die("Error publishing actual CRL: $error_description");
}
// ---------------------------------
// Get actual CRL
$ch = curl_init($ca_api_url . '/crl/last');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Authorization: Bearer ' .$access_token
));
// execute request
$output = curl_exec($ch);
$error_description = curl_error($ch);
curl_close($ch);
// check result
if (false === $output) {
die("Error publishing actual CRL: $error_description");
}
$crl = json_decode($output, true)['payload']['content'];
// ---------------------------------
// Build like a PEM
$splitted = str_split($crl, 64);
$pem_crl = "-----BEGIN CRL-----\n";
foreach ($splitted as $part) {
$pem_crl .= $part ."\n";
}
$pem_crl .= "-----END CRL-----";
print "The last CRL:\n$pem_crl\n";
// ---------------------------------
// publish
file_put_contents($publish_location, $pem_crl);
print "Done\n";
?>
8.4 Установка Web UI
- копируем файлы Web UI в корневой каталог Apache2
sudo cp ./pc-ca-ui/* /var/www/html/
sudo chown -R www-data:www-data /var/www/html/
- в файле
/var/www/html/config/app.config.jsonвносим настройкиorgName- название УЦserver.baseUrl- URL сервиса CA Core в CA GatewaykeyCloak.config- конфигурация KeyCloak (URL, Realm и Пользователь, созданный на шаге 5.2)
{
"orgName": "ST CA Local Stand",
"server": {
"baseUrl": "https://o-ca-stand.loc:9001/ca-core/api/v1"
},
"keyCloak": {
"init": {
"flow": "standard",
"checkLoginIframe": false,
"onLoad": "login-required"
},
"config": {
"url": "https://o-ca-stand.loc:8443/",
"realm": "st-ca",
"clientId": "ca-ui"
}
},
"template": {
"defaults": {
"msSubjectNameFlags": 1,
"msEnrollmentFlags": 1,
"msGeneralFlags": 0,
"msPrivateKeyFlags": 1
}
}
}
9. Установка и настройка MS WSTEP (CEP / CES)
9.1 Настройка интеграции Keycloak с доменом
9.1.1 Подготовка сервисной учетной записи в домене
- создаём в AD пользователя, из-под которого будет работать
ca-gateway, напримерca-gateway - регистрируем Service Principal Name (SPN) для этого пользователя в PowerShell от имени Администратора
setspn -A HTTP/[CA-Gateway-DNS-Address] [user-name]
Например,
setspn -A HTTP/o-ca-stand.loc ca-gateway
- формируем для этого пользователя keytab
ktpass -out [user-name].keytab -princ HTTP/[CA-Gateway-DNS-Address]@[FULL-DOMAIN-NAME] -mapUser [user-name]@[FULL-DOMAIN-NAME] -pass [user-pass] -kvno 0 -ptype KRB5_NT_PRINCIPAL -crypto AES256-SHA1
Например,
ktpass -out ca-gateway.keytab -princ HTTP/o-ca-stand.loc@DOMAIN.O-CA-STAND.LOC -mapUser ca-gateway@DOMAIN.O-CA-STAND.LOC -pass techpass -kvno 0 -ptype KRB5_NT_PRINCIPAL -crypto AES256-SHA1
Сохраняем полученный файл с keytab - в нашем примере ca-gateway.keytab
- включаем для пользователя поддержку нормальных схем шифрования ключей
AD Users & Computers -> [username] -> Properties -> Account -> галочкаThis account supports Kerberos AES 256 bit encryption
9.1.2 Базовая интеграция для синхронизации пользователей
В панели администрирования Keycloak выполняем следующие действия:
- в нашем realm переходим в User Federation
- Add New Provider, Vendor -
Active Directory - Connection URL -
ldap://[domain-controller-dns-name](например,ldap://o-ca-stand-dc.loc) - нажимаем
Test Connection, проверяем результат - Bind Type -
simple - Bind DN - DN пользователя с Административными правами (например,
CN=Administrator,CN=Users,DC=domain,DC=o-ca-stand,DC=loc) - Bind credentials - пароль пользователя
- нажимаем
Test authentication, проверяем результат - Edit mode -
READ_ONLYдля односторонней синхронизации - Users DN - в какой группе искать пользователей для синхронизации (например,
CN=Users,DC=domain,DC=o-ca-stand,DC=loc) - Username LDAP attribute - по какому полю маппить имя пользователя (например,
cn) - остальное оставляем как есть
- Synchronization settings настраиваем для своих нужд
- Allow Kerberos auth - включаем
- Kerberos realm -
[FULL-DOMAIN-NAME](например,DOMAIN.O-CA-STAND.LOC) - Server principal - полное имя SPN, зарегистрированного на шаге 9.1.1 (например,
HTTP/o-ca-stand.loc@DOMAIN.O-CA-STAND.LOC) - закидываем keytab-файл на сервер Keycloak
- Key tab - расположение файла keytab в локальной файловой системе (закидываем туда keytab-файл и указываем путь), например
/opt/keycloak/conf/keytab/ca-gateway.keytab
- Остальное оставляем как есть
- Галочка
Debug- при возникновении сложностей, ход взаимодействия по Kerberos будет выводиться в лог
9.2 Установка CEP / CES
Сервис CEP (политики выдачи сертификатов) устанавливается аналогично другим сервисам
- при необходимости создаём пользователя (см. п. 2) в ОС
- копируем файлы сервиса в
/opt/st-ca/ca-cep, присваиваем нужные права через chown - создаём сервис-файл
- при необходимости создаём скрипт-обёртку (см. п. 2)
- создаём конфигурацию, указываем адрес Discovery-сервиса
sudo nano /opt/st-ca/ca-cep/application.yml
server:
port: 9002
eureka:
client:
serviceUrl:
defaultZone: http://o-ca-stand.loc:8761/eureka/
instance:
preferIpAddress: false
logging:
level:
root: info
tech.paycon: info
Сервис CES (выпуск сертификатов) устанавливается аналогично другим сервисам
- при необходимости создаём пользователя (см. п. 2) в ОС
- копируем файлы сервиса в
/opt/st-ca/ca-ces, присваиваем нужные права через chown - создаём сервис-файл
- при необходимости создаём скрипт-обёртку (см. п. 2)
- создаём конфигурацию, указываем адрес Discovery-сервиса
sudo nano /opt/st-ca/ca-ces/application.yml
server:
port: 9003
eureka:
client:
serviceUrl:
defaultZone: http://o-ca-stand.loc:8761/eureka/
instance:
preferIpAddress: false
logging:
level:
root: info
tech.paycon: info
9.3 Контроль работы интеграции с AD
- в браузере открываем URL CEP, например
https://o-ca-stand.loc:9001/ca-cep/(это адресca-gatewayс указанием сервисаca-cep) - нас должно перебросить на страницу входа в KeyCloak
- вводим имя пользователя и пароль Администратора домена (или любого другого пользователя)
- аутентификация должна пройти успешно, мы должны оказаться на странице CEP с ошибкой
9.4 Маппинг ролей
В CA есть три роли - CaAdministrator, CaOperator, CaAuditor. Роль проверяется при выполнении тех или иных действий.
Чтобы пользователь мог, например, прочитать список шаблонов, у него должна быть одна из этих ролей.
Для этого необходимо настроить маппинг ролей из AD в роли CA.
Самый простой способ это сделать - создать Security Groups в AD и добавить туда пользователей.
Для этого
- открываем
Users and Computersна контроллере домена - создаём новый
OU, например,CaRoles - создаём в нём 3 новые группы -
CaAdministrator,CaOperator,CaUser - добавляем нужных пользователей в нужные группы (например
AdministratorвCaAdministrator) - идём в Keycloak
- User federation -> наш домен -> Mappers -> Add mapper
- Mapper type:
role-ldap-mapper - LDAP Roles DN: полный DN OU с ролями (например,
OU=CaRoles,DC=domain,DC=o-ca-stand,DC=loc) - Mode: можно выбрать
READ_ONLY - Client ID: идентификатор клиента, созданного в Keycloak на шаге 4 (например,
ca-gateway) - Сохраняем
- Users -> ищем всех (*) -> выбираем
administrator(доменный) ->Roles mapping-> убеждаемся, что появилсяCaAdministrator
9.5 Настройка политики выпуска сертификатов для пользователя
- в учетной записи пользователя домена (например, Администратора), открываем консоль управления сертификатами
certmgr.msc - в вкладке Personal -> Certificates тыкаем правой кнопкой мыши -> Additional Actions -> Manage Policies
- добавляем адрес CEP -
https://[ca-gateway]/ca-cep/services/policy/GetPoliciesнапример,https://o-ca-stand.loc:9001/ca-cep/services/policy/GetPolicies - нажимаем
Validate- ошибок быть не должно - следующим шагом выпускаем сертификат по только что добавленной политике
Замечания
- Чтобы выпускать сертификаты через CEP/CES для пользователей значение
msGeneralFlagsв шаблоне должно быть 0 или (согласно доке MS) - 0x00000080. Но второй вариант в Win Server 2022 не срабатывает - Для пользователя наиболее приемлемый keyUsage - 496 (Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment)
- При возникновении ошибок при добавлении политики можно анализировать логи:
- Events Viewer -> Application and Services Logs -> Microsoft -> Windows -> CAPI2 | WebServices
journalctl -u keycloak.service -f -o cat -n 500journalctl -u ca-eureka.service -u ca-core.service -u ca-gateway.service -u ca-ces.service -u ca-cep.service -f -o cat -n 500
- Статья для понимания принципов работы с MS CEP/CES в рамках AD