developer_guide Quick Start JWT - OUT OF SCOPE!¶
OUT OF SCOPE! This feature is not Included in the scope of this project
This section contains a Quick Start developer_guide for the Secure PVAccess Java Web Token (JWT) Authenticator.
The JWT Authenticator is an Authenticator that uses a JSON Web Token (JWT) to create an X.509 certificate.
It verifies the token’s signature and claims (such as the issuer and expiration time) to ensure the token’s authenticity.
The token’s subject (
sub) is used as the certificate’s Common Name, and the token’s issuer (iss) (or associated domain) is used for the Organization field (the Organizational Unit is left blank by default).The token is sent to the PVACMS, which validates the token (checking the signature and payload against the trusted issuer). If the token is valid, PVACMS generates a signed certificate in the
VALIDstate using the token’s identity information.
Our starting point for this Quick Start Guide is the end of the Quick Start Std so if you haven’t gone through it yet
do that now then come back here. You need to have users’s configured (pvacms, admin, client, and client).
We will set up a containerized JWT environment to simulate an identity provider and issue tokens for our users.
We will also configure PVACMS to trust the JWT issuer’s validation URI. Then we’ll use a JWT to obtain certificates for a
server and a client, and demonstrate a Secure PVAccess connection using those certificates.
See Secure PVAccess for general documentation on Secure PVAccess.
Other Quick Start Guides:
local_library You will learn:¶
deployed_codePrepackaged¶
If you want a prepackaged environment, try the following. You will need three terminal sessions.
counter_1 Load image¶
computer¹
Start the container with a prepackaged Secure PVAccess environment that includes JWT support
docker run -it --name spva_jwt georgeleveln/spva_jwt:latest
2025-03-08 14:40:43,319 CRIT Supervisor is running as root. Privileges were not dropped because no user is specified in the config file. If you intend to run as root, you can set user=root in the config file to avoid this message.
2025-03-08 14:40:43,319 INFO Included extra file "/etc/supervisor/conf.d/jwt-issuer.conf" during parsing
2025-03-08 14:40:43,319 INFO Included extra file "/etc/supervisor/conf.d/pvacms.conf" during parsing
2025-03-08 14:40:43,322 INFO supervisord started with pid 1
2025-03-08 14:40:44,334 INFO spawned: 'jwt-issuer' with pid 7
2025-03-08 14:40:44,346 INFO spawned: 'pvacms' with pid 9
2025-03-08 14:40:45,589 INFO success: jwt5-issuer entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2025-03-08 14:40:45,589 INFO success: pvacms entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
counter_2 Service¶
computer²
log in as service account
docker exec -it --user softioc spva_jwt /bin/bash
get a token. Use “secret” as the password
TOKEN=$(curl -s "http://localhost:8080/default/token?sub=softioc&password=secret")
echo ${TOKEN} > token_file
curl -sG --data-urlencode "token=$TOKEN" "http://localhost:8080/default/verify"
{"claims":{"aud":"default","exp":1755150736,"iat":1755147136,"iss":"http://localhost:8080/default","jti":"c40d1fc2-40f2-4bf9-a84a-fff5fadea38a","nbf":1755147136,"sub":"softioc"},"header":{"alg":"RS256","kid":"demo-key-1","typ":"JWT"},"valid":true}
create a server certificate using the Java Web Token (JWT) Authenticator
authnjwt -u server --token-file token_file
Keychain file created : /home/softioc/.config/pva/1.3/server.p12
Certificate identifier : 47530d89:3826361579604613180
pvxcert -f ~/.config/pva/1.3/server.p12
Certificate Details:
============================================
Certificate ID : 47530d89:3826361579604613180
Entity Subject : CN=softioc, O=localhost
Issuer Subject : CN=EPICS Root Certificate Authority, C=US, O=certs.epics.org, OU=EPICS Certificate Authority
Valid From : Sat Mar 08 15:23:21 2025 UTC
Expires On : Sun Mar 09 15:23:09 2025 UTC
--------------------------------------------
Certificate Status:
============================================
Certificate ID: 47530d89:3826361579604613180
Status : VALID
Status Issued : Sat Mar 08 15:47:14 2025 UTC
Status Expires: Sat Mar 08 16:17:14 2025 UTC
--------------------------------------------
counter_3 Client¶
computer³
log in as a Secure PVAccess client
docker exec -it --user client spva_jwt /bin/bash
get a token. Use “secret” as the password
TOKEN=$(curl -s "http://localhost:8080/default/token?sub=client&password=secret")
echo ${TOKEN} > token_file
curl -sG --data-urlencode "token=$TOKEN" "http://localhost:8080/default/verify"
{"claims":{"aud":"default","exp":1755150140,"iat":1755146540,"iss":"http://localhost:8080/default","jti":"c7cad85c-ae49-49cc-abf7-c3be923ce06b","nbf":1755146540,"sub":"client"},"header":{"alg":"RS256","kid":"demo-key-1","typ":"JWT"},"valid":true}
{ "valid": true, "claims": { "sub": "client", ... } }
create a client certificate using the Java Web Token (JWT) Authenticator
authnjwt --token-file token_file
Keychain file created : /home/client/.config/pva/1.3/client.p12
Certificate identifier : 47530d89:15177030356392297708
pvxcert -f ~/.config/pva/1.3/client.p12
Certificate Details:
============================================
Certificate ID : 47530d89:15177030356392297708
Entity Subject : CN=client, O=localhost
Issuer Subject : CN=EPICS Root Certificate Authority, C=US, O=certs.epics.org, OU=EPICS Certificate Authority
Valid From : Sat Mar 08 15:31:44 2025 UTC
Expires On : Sun Mar 09 15:27:50 2025 UTC
--------------------------------------------
Certificate Status:
============================================
Certificate ID: 47530d89:15177030356392297708
Status : VALID
Status Issued : Sat Mar 08 15:40:20 2025 UTC
Status Expires: Sat Mar 08 16:10:20 2025 UTC
--------------------------------------------
counter_4 Start SoftIOC¶
computer²
start SoftIOC
softIocPVX \
-m user=test,N=tst,P=tst \
-d ${PROJECT_HOME}/pvxs/test/testioc.db \
-d ${PROJECT_HOME}/pvxs/test/testiocg.db \
-d ${PROJECT_HOME}/pvxs/test/image.db \
-G ${PROJECT_HOME}/pvxs/test/image.json \
-a ${PROJECT_HOME}/pvxs/test/testioc.tls.acf
INFO: PVXS QSRV2 is loaded, permitted, and ENABLED.
2025-03-08T15:36:11.265341125 WARN pvxs.tcp.init Server unable to bind TCP port 5075, falling back to [::]:39377
2025-03-08T15:36:11.265436375 WARN pvxs.tcp.init Server unable to bind TLS port 5076, falling back to [::]:34381
Starting iocInit
############################################################################
## EPICS R7.0.8.2-DEV
## Rev. R7.0.8.1-123-g48607a42586b1a316cd6
## Rev. Date Git: 2024-11-29 17:08:28 +0000
############################################################################
iocRun: All initialization complete
epics>
counter_5 Get PV value¶
computer³
get a PV
test:enumExamplevalue from the SoftIOC
pvxinfo -v test:enumExample
Effective config
EPICS_PVA_AUTO_ADDR_LIST=YES
EPICS_PVA_BROADCAST_PORT=5076
EPICS_PVA_CONN_TMO=30
EPICS_PVA_SERVER_PORT=5075
EPICS_PVA_TLS_KEYCHAIN=/home/client/.config/pva/1.3/client.p12
EPICS_PVA_TLS_OPTIONS=on_expiration=fallback-to-tcp on_no_cms=fallback-to-tcp
EPICS_PVA_TLS_PORT=5076
XDG_CONFIG_HOME=/home/client/.config/pva/1.3
XDG_DATA_HOME=/home/client/.local/share/pva/1.3
# TLS x509:47530d89:3826361579604613181:EPICS Root Certificate Authority/client@172.17.0.2:34381
test:enumExample from 172.17.0.2:34381
struct "epics:nt/NTEnum:1.0" {
struct "enum_t" {
int32_t index
string[] choices
} value
struct "alarm_t" {
int32_t severity
int32_t status
string message
} alarm
struct "time_t" {
int64_t secondsPastEpoch
int32_t nanoseconds
int32_t userTag
} timeStamp
struct {
string description
} display
}
verify that connection is TLS
TLS x509:47530d89:3826361579604613181:EPICS Root Certificate Authority/client @ 172.17.0.2indicates that:The connection is
TLS,The Server end of the channel has been authenticated by the Root Certificate Authority
EPICS Root Certificate AuthorityThe Server end of the channel’s name has been authenticated as
clientand is connecting from host172.17.0.2
checklist Step-By-Step¶
Env. pvacms |
Params. pvacms |
Keys and Values |
Description |
|---|---|---|---|
EPICS_AUTH_JWT_REQUEST_FORMAT |
|
string format for verification request payload e.g. e.g. |
A string that is used verbatim as the payload for the verification
request while substituting the string |
EPICS_AUTH_JWT_REQUEST_METHOD |
|
e.g. of call made for GET: GET /api/validate-token HTTP/1.1 Authorization: Bearer eyJhbGcXVCJ9… |
This determines whether the endpoint will be called with
|
EPICS_AUTH_JWT_RESPONSE_FORMAT |
|
string format for verification response value
e.g. |
A pattern string that we can use to decode the response from a
verification endpoint if the response is formatted text. All white
space is removed in the given string and in the response. Then all
the text prior to |
EPICS_AUTH_JWT_TRUSTED_URI |
|
uri of JWT validation endpoint e.g. |
Trusted URI of the validation endpoint including the |
EPICS_AUTH_JWT_USE_RESPONSE_CODE |
|
case insensitive:
|
If set this tells PVACMS that when it receives a |
Env. authnjwt |
Params. authjwt |
Keys and Values |
Description |
|---|---|---|---|
EPICS_AUTH_JWT_FILE |
|
location of JWT file
e.g. |
file containing JWT token text |
done_all Docker Image¶
counter_1 Use a Prepackaged spva_std image¶
computer¹
open a terminal and load pre-built image
don’t forget to add /bin/bash at the end to suppress running the pvacms
docker run -it --name spva_jwt georgeleveln/spva_std:latest /bin/bash
done_all JWT Issuer & Validator¶
This section shows how to install and configure a Java Web Token (JWT) Issuer & Validator. This is included to enable you to test the Java Web Token (JWT) Authenticator before deploying it into your network. It will enable you to configure EPICS agents that have valid JWTs that can be exchanged for X.509 certificates using the Java Web Token (JWT) Authenticator.
counter_1 Install prerequisites¶
Add Python to run our JWT Issuer and Validator
python3
- python3-pip - for package management in python
flask
pyjwt package for parsing JTWs
cryptography package for cryptographic keys
apt-get update && \
apt-get install -y \
python3 python3-pip \
python3-flask python3-jwt python3-cryptography \
curl && \
rm -rf /var/lib/apt/lists/*
Hit:1 http://ports.ubuntu.com/ubuntu-ports noble InRelease
Get:2 http://ports.ubuntu.com/ubuntu-ports noble-updates InRelease [126 kB]
Get:3 http://ports.ubuntu.com/ubuntu-ports noble-backports InRelease [126 kB]
Get:4 http://ports.ubuntu.com/ubuntu-ports noble-security InRelease [126 kB]
Get:5 http://ports.ubuntu.com/ubuntu-ports noble-updates/multiverse arm64 Packages [39.2 kB]
Get:6 http://ports.ubuntu.com/ubuntu-ports noble-updates/universe arm64 Packages [1422 kB]
Get:7 http://ports.ubuntu.com/ubuntu-ports noble-updates/main arm64 Packages [1705 kB]
Get:8 http://ports.ubuntu.com/ubuntu-ports noble-updates/restricted arm64 Packages [2704 kB]
Get:9 http://ports.ubuntu.com/ubuntu-ports noble-backports/main arm64 Packages [48.8 kB]
Get:10 http://ports.ubuntu.com/ubuntu-ports noble-backports/universe arm64 Packages [37.2 kB] ...
counter_2 Rebuild pvxs¶
enable Java Web Token (JWT) Authenticator by updating
CONFIG_SITE.localdo a clean rebuild of pvxs
export PROJECT_HOME=/opt/epics
cd ${PROJECT_HOME}
cat >> CONFIG_SITE.local <<EOF
EVENT2_HAS_OPENSSL = YES
PVXS_ENABLE_PVACMS = YES
PVXS_ENABLE_JWT_AUTH = YES
EOF
cd pvxs && \
make distclean && make -j10 all
make -C ./configure realclean
make[1]: Entering directory '/opt/epics/pvxs/configure'
rm -rf O.*
make[1]: Leaving directory '/opt/epics/pvxs/configure'
make -C ./setup realclean
make[1]: Entering directory '/opt/epics/pvxs/setup'
rm -rf O.*
make[1]: Leaving directory '/opt/epics/pvxs/setup'
make -C ./src realclean
make[1]: Entering directory '/opt/epics/pvxs/src'
rm -rf O.*
make[1]: Leaving directory '/opt/epics/pvxs/src'
make -C ./tools realclean
make[1]: Entering directory '/opt/epics/pvxs/tools'
rm -rf O.*
make[1]: Leaving directory '/opt/epics/pvxs/tools'
...
/usr/bin/g++ -o testtlswithcms -L/opt/epics/epics-base/lib/linux-aarch64 -L/opt/epics/pvxs/lib/linux-aarch64 -Wl,-rpath,/opt/epics/epics-base/lib/linux-aarch64 -Wl,-rpath,/opt/epics/pvxs/lib/linux-aarch64 -Wl,--as-needed -Wl,--compress-debug-sections=zlib -rdynamic testtlswithcms.o certstatusfactory.o certstatusmanager.o certstatus.o -lpvxs -lCom -levent_openssl -levent_core -levent_pthreads -lssl -lcrypto
perl -CSD /opt/epics/epics-base/bin/linux-aarch64/makeTestfile.pl linux-aarch64 linux-aarch64 testtlswithcms.t testtlswithcms
make[2]: Leaving directory '/opt/epics/pvxs/test/O.linux-aarch64'
make[1]: Leaving directory '/opt/epics/pvxs/test'
counter_3 Configure JWT Issuer and Validator¶
configure the JWT Issuer and Validator
create application
cat > /opt/epics/app.py <<EOF
import base64
import os
import uuid
from datetime import datetime, timedelta, timezone
from pathlib import Path
from flask import Flask, request, jsonify, Response
import jwt
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
app = Flask(__name__)
# --- Config (env overridable) ---
PORT = int(os.getenv("PORT", "8080"))
DEFAULT_ISS = os.getenv("ISSUER", "http://localhost:8080/default")
DEFAULT_AUD = os.getenv("DEFAULT_AUDIENCE", "default")
DEFAULT_EXP_SECS = int(os.getenv("DEFAULT_EXP_SECS", "3600"))
ALGO = os.getenv("ALGO", "RS256") # RS256 (default) or HS256
KEY_DIR = Path(os.getenv("KEY_DIR", "/data/keys"))
KID = os.getenv("KID", "demo-key-1")
HS_SECRET = os.getenv("HS_SECRET", "dev-secret-change-me") # only used if ALGO=HS256
# --- Keys (ephemeral by default; files used only if writable and present) ---
_priv_key = None
_pub_key = None
def _b64u(data: bytes) -> str:
return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
def _ensure_keys():
"""Generate or load RSA keys for RS256. HS256 uses HS_SECRET."""
global _priv_key, _pub_key
if ALGO.upper() == "HS256":
return # nothing to do
KEY_DIR.mkdir(parents=True, exist_ok=True)
priv_path = KEY_DIR / "id_rsa.pem"
pub_path = KEY_DIR / "id_rsa.pub"
if priv_path.exists() and pub_path.exists():
_priv_key = serialization.load_pem_private_key(priv_path.read_bytes(), password=None)
_pub_key = serialization.load_pem_public_key(pub_path.read_bytes())
return
# Generate ephemeral RSA keypair
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
pub = key.public_key()
_priv_key = key
_pub_key = pub
# Best-effort write (ok if it fails; we remain in-memory)
try:
priv_bytes = key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)
pub_bytes = pub.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
priv_path.write_bytes(priv_bytes)
pub_path.write_bytes(pub_bytes)
except Exception:
pass
def _current_priv_key():
return HS_SECRET if ALGO.upper() == "HS256" else _priv_key
def _current_pub_key():
return HS_SECRET if ALGO.upper() == "HS256" else _pub_key
def _rsa_jwk():
if ALGO.upper() == "HS256" or _pub_key is None:
return []
nums = _pub_key.public_numbers()
n = _b64u(nums.n.to_bytes((nums.n.bit_length() + 7) // 8, "big"))
e = _b64u(nums.e.to_bytes((nums.e.bit_length() + 7) // 8, "big"))
return [{
"kty": "RSA",
"kid": KID,
"alg": "RS256",
"use": "sig",
"n": n,
"e": e,
}]
# Initialize keys at import
_ensure_keys()
@app.get("/healthz")
def healthz():
return {"status": "ok"}
# Issue: GET /default/token?sub=client&password=secret[&aud=...&iss=...&exp_secs=...]
@app.get("/default/token")
def issue_token():
sub = request.args.get("sub")
pwd = request.args.get("password")
if not sub:
return jsonify(error="missing 'sub'"), 400
if pwd != "secret":
return "Forbidden", 403
aud = request.args.get("aud", DEFAULT_AUD)
iss = request.args.get("iss", DEFAULT_ISS)
exp_secs = int(request.args.get("exp_secs", DEFAULT_EXP_SECS))
now = datetime.now(timezone.utc)
claims = {
"sub": sub,
"iss": iss,
"aud": aud,
"iat": int(now.timestamp()),
"nbf": int(now.timestamp()),
"exp": int((now + timedelta(seconds=exp_secs)).timestamp()),
"jti": str(uuid.uuid4()),
}
headers = {"kid": KID, "typ": "JWT"}
token = jwt.encode(
claims,
_current_priv_key(),
algorithm=ALGO,
headers=headers,
)
# Return RAW token (text/plain), not JSON
return Response(token, mimetype="text/plain")
# Verify: GET /default/verify?token=... [&expected_iss=...][&expected_aud=...]
@app.get("/default/verify")
def verify_token():
token = request.args.get("token")
if not token:
return jsonify(valid=False, error="missing 'token'"), 400
expected_iss = request.args.get("expected_iss", DEFAULT_ISS)
expected_aud = request.args.get("expected_aud") # optional
options = {
"require": ["exp", "iat", "nbf", "iss", "sub"],
"verify_aud": bool(expected_aud),
}
try:
decoded = jwt.decode(
token,
_current_pub_key(),
algorithms=[ALGO],
issuer=expected_iss,
audience=expected_aud if expected_aud else None,
options=options,
)
header = jwt.get_unverified_header(token)
return jsonify(valid=True, header=header, claims=decoded)
except Exception as e:
return jsonify(valid=False, error=str(e))
# JWKS
@app.get("/.well-known/jwks.json")
@app.get("/default/jwks")
def jwks():
return jsonify({"keys": _rsa_jwk()})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=PORT)
EOF
counter_4 Configure PVACMS for Java Web Token (JWT) Authenticator¶
set up environment for pvacms - request contains just token, no JSON - use HTTP GET with parameter =
tokenset to token value. - the response will be any valid JSON with a tagvalidwhose value will indicate whether the token is valid - specify the url to use to verify tokens http://localhost:8080/default/verify. - for verification token is URL encoded as parametertoken
cat >> /home/pvacms/.spva_jwt_bashrc <<EOF
export EPICS_AUTH_JWT_REQUEST_FORMAT='#token#'
export EPICS_AUTH_JWT_REQUEST_METHOD='GET'
export EPICS_AUTH_JWT_RESPONSE_FORMAT='{ *, "valid": "#response#" }'
export EPICS_AUTH_JWT_TRUSTED_URI='http://localhost:8080/default/verify'
#export EPICS_AUTH_JWT_USE_RESPONSE_CODE=NO
EOF
set up pvacms to run this new config
echo "source ~/.spva_jwt_bashrc" >> /home/pvacms/.bashrc
counter_5 Configure Supervisor to run the JWT authenticator¶
configure jwt_issuer supervisord
cat > /etc/supervisor/conf.d/jwt_issuer.conf <<EOF
[program:jwt-issuer]
command=python3 /opt/epics/app.py
autostart=true
autorestart=true
stdout_logfile=/var/log/supervisor/jwt-issuer.out.log
stderr_logfile=/var/log/supervisor/jwt-issuer.err.log
EOF
counter_6 Start Services¶
update pvacms supervisor config to include Java Web Token (JWT) Authenticator configuration
cat >> /etc/supervisor/conf.d/pvacms.conf <<EOF
environment=EPICS_AUTH_JWT_RESPONSE_FORMAT='{ *, "valid": "#response#" }',EPICS_AUTH_JWT_TRUSTED_URI="http://localhost:8080/default/verify"
EOF
start jwt_issuer, and pvacms with Java Web Token (JWT) Authenticator support
/usr/bin/supervisord -c /etc/supervisor/supervisord.conf
2025-08-14 05:38:25,124 INFO Included extra file "/etc/supervisor/conf.d/jwt_issuer.conf" during parsing
2025-08-14 05:38:25,124 INFO Included extra file "/etc/supervisor/conf.d/pvacms.conf" during parsing
2025-08-14 05:38:25,124 INFO Set uid to user 0 succeeded
2025-08-14 05:38:25,125 INFO supervisord started with pid 2774
2025-08-14 05:38:26,136 INFO spawned: 'jwt-issuer' with pid 2775
2025-08-14 05:38:26,138 INFO spawned: 'pvacms' with pid 2776
2025-08-14 05:38:27,493 INFO success: jwt-issuer entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2025-08-14 05:38:27,493 INFO success: pvacms entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
done_all Run SoftIOC¶
counter_1 Login as softioc in a new shell¶
computer²
docker exec -it --user softioc spva_jwt /bin/bash
counter_3 Get JWT (token)¶
get a token. Use “secret” as the password
TOKEN=$(curl -s "http://localhost:8080/default/token?sub=softioc&password=secret")
echo ${TOKEN} > token_file
curl -sG --data-urlencode "token=$TOKEN" "http://localhost:8080/default/verify"
{"claims":{"aud":"default","exp":1755150736,"iat":1755147136,"iss":"http://localhost:8080/default","jti":"c40d1fc2-40f2-4bf9-a84a-fff5fadea38a","nbf":1755147136,"sub":"softioc"},"header":{"alg":"RS256","kid":"demo-key-1","typ":"JWT"},"valid":true}
counter_3 Get Certificate¶
create a client server certificate
creates client server certificate
at location specified by
EPICS_PVAS_TLS_KEYCHAINor${XDG_CONFIG_HOME}/pva/1.3/server.p12by default
authnjwt -u server --token-file token_file
Keychain file created : /home/client/.config/pva/1.3/server.p12
Certificate identifier : b271f07a:12421554925305118824
counter_4 Check the certificate status is VALID¶
check that the generated certificate is
VALIDnote that the name is
softioc- picked up fromsubin the JWTnote that the organization is
localhost- picked up from the issuer domainnote that the expiration date is the same as the
expof the JWTnote that the start date is set to the
nbfof the JWT
pvxcert -f ~/.config/pva/1.3/server.p12
Certificate Details:
============================================
Certificate ID : 47530d89:3826361579604613180
Entity Subject : CN=softioc, O=localhost
Issuer Subject : CN=EPICS Root Certificate Authority, C=US, O=certs.epics.org, OU=EPICS Certificate Authority
Valid From : Sat Mar 08 15:23:21 2025 UTC
Expires On : Sun Mar 09 15:23:09 2025 UTC
--------------------------------------------
Certificate Status:
============================================
Certificate ID: 47530d89:3826361579604613180
Status : VALID
Status Issued : Sat Mar 08 15:47:14 2025 UTC
Status Expires: Sat Mar 08 16:17:14 2025 UTC
--------------------------------------------
counter_5 Run Secure PVAccess Service¶
start the service
softIocPVX \
-m user=test,N=tst,P=tst \
-d ${PROJECT_HOME}/pvxs/test/testioc.db \
-d ${PROJECT_HOME}/pvxs/test/testiocg.db \
-d ${PROJECT_HOME}/pvxs/test/image.db \
-G ${PROJECT_HOME}/pvxs/test/image.json \
-a ${PROJECT_HOME}/pvxs/test/testioc.tls.acf
INFO: PVXS QSRV2 is loaded, permitted, and ENABLED.
2025-03-10T03:28:17.264206926 WARN pvxs.tcp.init Server unable to bind TCP port 5075, falling back to [::]:46831
2025-03-10T03:28:17.264284426 WARN pvxs.tcp.init Server unable to bind TLS port 5076, falling back to [::]:37027
Starting iocInit
############################################################################
## EPICS R7.0.8.2-DEV
## Rev. R7.0.8.1-123-g48607a42586b1a316cd6
## Rev. Date Git: 2024-11-29 17:08:28 +0000
############################################################################
iocRun: All initialization complete
epics>
done_all SPVA Client¶
counter_1 Login as client in a new shell¶
computer³
docker exec -it --user client spva_jwt /bin/bash
counter_2 Get JWT (token)¶
get a token. Use “secret” as the password
TOKEN=$(curl -s "http://localhost:8080/default/token?sub=client&password=secret")
echo ${TOKEN} > token_file
curl -sG --data-urlencode "token=$TOKEN" "http://localhost:8080/default/verify"
{"claims":{"aud":"default","exp":1755150140,"iat":1755146540,"iss":"http://localhost:8080/default","jti":"c7cad85c-ae49-49cc-abf7-c3be923ce06b","nbf":1755146540,"sub":"client"},"header":{"alg":"RS256","kid":"demo-key-1","typ":"JWT"},"valid":true}
{ "valid": true, "claims": { "sub": "client", ... } }
counter_3 Get Certificate¶
create a client certificate
creates a client certificate
at location specified by
EPICS_PVA_TLS_KEYCHAINor${XDG_CONFIG_HOME}/pva/1.3/client.p12by default
authnjwt --token-file token_file
Keychain file created : /home/client/.config/pva/1.3/client.p12
Certificate identifier : b271f07a:1204731550645534180
counter_4 Check the certificate status is VALID¶
check that the generated certificate is
VALIDnote that the name is
client- picked up fromsubin the JWTnote that the organization is
localhost- picked up from the issuer domainnote that the expiration date is the same as the
expof the JWTnote that the start date is set to the
nbfof the JWT
pvxcert -f ~/.config/pva/1.3/client.p12
Certificate Details:
============================================
Certificate ID : b271f07a:1204731550645534180
Entity Subject : CN=client, O=localhost
Issuer Subject : CN=EPICS Root Certificate Authority, C=US, O=certs.epics.org, OU=EPICS Certificate Authority
Valid From : Mon Mar 10 03:32:57 2025 UTC
Expires On : Tue Mar 11 03:30:32 2025 UTC
--------------------------------------------
Certificate Status:
============================================
Certificate ID: b271f07a:1204731550645534180
Status : VALID
Status Issued : Mon Mar 10 03:33:58 2025 UTC
Status Expires: Mon Mar 10 04:03:58 2025 UTC
--------------------------------------------
counter_5 Test TLS client operations¶
pvxget -F tree test:structExample
test:structExample
...
show that TLS is being used
pvxinfo -v test:enumExample
Effective config
EPICS_PVA_AUTO_ADDR_LIST=YES
EPICS_PVA_BROADCAST_PORT=5076
EPICS_PVA_CONN_TMO=30
EPICS_PVA_SERVER_PORT=5075
EPICS_PVA_TLS_KEYCHAIN=/home/client/.config/pva/1.3/client.p12
EPICS_PVA_TLS_OPTIONS=on_expiration=fallback-to-tcp on_no_cms=fallback-to-tcp
EPICS_PVA_TLS_PORT=5076
XDG_CONFIG_HOME=/home/client/.config/pva/1.3
XDG_DATA_HOME=/home/client/.local/share/pva/1.3
# TLS x509:b271f07a:12421554925305118824:EPICS Root Certificate Authority/client@172.17.0.2:37027
test:enumExample from 172.17.0.2:37027
struct "epics:nt/NTEnum:1.0" {
struct "enum_t" {
int32_t index
string[] choices
} value
struct "alarm_t" {
int32_t severity
int32_t status
string message
} alarm
struct "time_t" {
int64_t secondsPastEpoch
int32_t nanoseconds
int32_t userTag
} timeStamp
struct {
string description
} display
}
Note
TLS x509:b271f07a:12421554925305118824:EPICS Root Certificate Authority/client @ 172.17.0.2indicates that:The connection is
TLS,The Server end of the channel has been authenticated by the Root Certificate Authority
EPICS Root Certificate AuthorityThe Server end of the channel’s name has been authenticated as
clientand is connecting from host172.17.0.2