Руководство по установке ST CA

Пререквизиты

Так как работа сервисов СА во многом завязана на 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
{
    "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
[
    "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
    • тело - запрос на выпуск сертификата
{
    "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
    • тело - запрос на выпуск сертификата
{
    "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
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 Gateway
    • keyCloak.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 - ошибок быть не должно
  • следующим шагом выпускаем сертификат по только что добавленной политике

Замечания

  1. Чтобы выпускать сертификаты через CEP/CES для пользователей значение msGeneralFlags в шаблоне должно быть 0 или (согласно доке MS) - 0x00000080. Но второй вариант в Win Server 2022 не срабатывает
  2. Для пользователя наиболее приемлемый keyUsage - 496 (Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment)
  3. При возникновении ошибок при добавлении политики можно анализировать логи:
    • Events Viewer -> Application and Services Logs -> Microsoft -> Windows -> CAPI2 | WebServices
    • journalctl -u keycloak.service -f -o cat -n 500
    • journalctl -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
  4. Статья для понимания принципов работы с MS CEP/CES в рамках AD