Phase 1
Establish the account foundation
Identify the account and organization and create the setup service identity and credentials.
Phase details
- Workstream
- Shared foundation · Step 1 of 2
- Product
- Account Manager
- Time
- 25 minutes
- Setup
- One-time setup
- Goal
- Create a service user with limited roles, capture its API token, and create its client authentication certificate.
- Inputs
bootstrap_api_tokenaccount_nameorganization_nameapproved_role_namesclient_auth_certificate_expiration- Expected outputs
account_idorganization_idservice_user_idapi_token_idservice_api_tokenclient_auth_certificate_idclient.crtclient.key
In this phase, you identify the target account and organization and find the required role names in your tenant. You then create the setup service user and both authentication factors required later by DigiCert® Software Trust Manager.
Phase prerequisites
Make sure you have:
- A bootstrap user with an API token for the target tenant, the Manage users permission in DigiCert® Account Manager, and access to the target account, organization, and assignable roles. Make the token available as
BOOTSTRAP_API_TOKENfor cURL orbootstrap_api_tokenfor Python. - The exact target account and organization names.
- Approved roles for the other products that contain the exact permissions listed in Before you begin.
- A secure destination for the service-user API token, client certificate, and private key.
- An approved UTC expiration timestamp for the client authentication credential.
Endpoints used
| Method | Path | Purpose |
|---|---|---|
| GET | /account/api/v1/account | List accessible accounts. |
| GET | /account/api/v1/role | List assignable roles by product. |
| GET | /account/api/v1/organization | List organizations in the target account. |
| POST | /account/api/v1/user | Create the service user and return its API token once. |
| POST | /account/api/v1/client-auth-certificate | Sign a certificate signing request (CSR) and create a client authentication certificate for the authenticated service user. |
| GET | /account/api/v1/user/me on the clientauth. host | Verify the new certificate authenticates as the service user. |
Step 1.1: Select the target account
GET /account/api/v1/account returns an array. Select the intended active account explicitly. Do not assume the response contains only one account.
ACCOUNT_NAME="Example account"
ACCOUNT_ID="$(
curl --fail-with-body --silent --show-error \
"https://demo.one.digicert.com/account/api/v1/account" \
-H "x-api-key: ${BOOTSTRAP_API_TOKEN}" |
jq -er --arg name "${ACCOUNT_NAME}" '
[.[] | select(.name == $name and .active == true)] |
if length == 1 then .[0].id
else error("expected one active target account")
end'
)"
export ACCOUNT_ID
printf '%s\n' "${ACCOUNT_ID}" > account-id.txt
printf 'Selected account: %s (%s)\n' "${ACCOUNT_NAME}" "${ACCOUNT_ID}"import requests
base_url = "https://demo.one.digicert.com"
response = requests.get(
f"{base_url}/account/api/v1/account",
headers={"x-api-key": bootstrap_api_token},
timeout=30,
)
response.raise_for_status()
accounts = response.json()
matches = [item for item in accounts if item["name"] == "TARGET_ACCOUNT_NAME" and item["active"]]
if len(matches) != 1:
raise RuntimeError(f"Expected one active target account. Found {len(matches)}")
account_id = matches[0]["id"]
print(f"Selected account: {matches[0]['name']} ({account_id})")The cURL example exports ACCOUNT_ID. The Python example assigns account_id.
Step 1.2: Find the required role names
Role names are tenant-dependent. Retrieve the roles and select the minimum set that grants the DigiCert® Private CA, DigiCert® Device Trust Manager, and Software Trust Manager operations listed in Before you begin.
curl --fail-with-body --silent --show-error \
"https://demo.one.digicert.com/account/api/v1/role?account_id=${ACCOUNT_ID}" \
-H "x-api-key: ${BOOTSTRAP_API_TOKEN}" |
jq '{ca_manager, device_trust_manager, secure_software_manager}'
Record the exact name values. Do not copy example role names from the API reference or another account.
Step 1.3: Select an organization
Software Trust Manager private trust certificate profiles associate issued certificates with an organization. Select the intended active organization by exact name.
ORGANIZATION_NAME="Example organization"
ORGANIZATION_ID="$(
curl --fail-with-body --silent --show-error \
"https://demo.one.digicert.com/account/api/v1/organization?account_id=${ACCOUNT_ID}" \
-H "x-api-key: ${BOOTSTRAP_API_TOKEN}" |
jq -er --arg name "${ORGANIZATION_NAME}" '
[.[] | select(.name == $name and .active == true)] |
if length == 1 then .[0].id
else error("expected one active target organization")
end'
)"
export ORGANIZATION_ID
printf '%s\n' "${ORGANIZATION_ID}" > organization-id.txt
printf 'Selected organization: %s (%s)\n' "${ORGANIZATION_NAME}" "${ORGANIZATION_ID}"
The example fails unless the approved name identifies exactly one active organization. It exports ORGANIZATION_ID as the shell equivalent of organization_id.
Step 1.4: Create the setup service user
Set ROLE_NAMES_JSON to an array of exact role name values from Find the required role names. The API returns api_token.token only once. Write the response to a protected file, and do not print the token in logs.
umask 077
ROLE_NAMES_JSON='[
"<private-ca-role-name>",
"<device-trust-role-name>",
"<software-trust-role-name>"
]'
jq -n \
--arg account_id "${ACCOUNT_ID}" \
--argjson roles "${ROLE_NAMES_JSON}" \
'{
user_type: "service",
friendly_name: "Private trust stack setup",
email: "pki-automation-owner@example.com",
description: "Setup identity for the private trust solution",
accounts: [$account_id],
roles: $roles
}' |
curl --fail-with-body --silent --show-error \
-X POST "https://demo.one.digicert.com/account/api/v1/user" \
-H "x-api-key: ${BOOTSTRAP_API_TOKEN}" \
-H "Content-Type: application/json" \
--data-binary @- \
-o service-user-response.json
jq -r '.api_token.token' service-user-response.json > service-api-token.txt
jq -r '.id' service-user-response.json > service-user-id.txt
jq -r '.api_token.id' service-user-response.json > api-token-id.txt
chmod 600 service-api-token.txt service-user-id.txt api-token-id.txt service-user-response.json
SERVICE_USER_ID="$(<service-user-id.txt)"
API_TOKEN_ID="$(<api-token-id.txt)"
SERVICE_API_TOKEN="$(<service-api-token.txt)"
export SERVICE_USER_ID API_TOKEN_ID
The successful response is an object containing the service user id and an api_token object:
{
"id": "5e0bd5fe-117f-4049-b686-1548b1ee5e14",
"status": "ACTIVE",
"friendly_name": "Private trust stack setup",
"api_token": {
"id": "cdfcf47d-b47f-4919-bd1b-c62935312cea",
"token": "<returned-once; redacted>",
"enabled": true
}
}
Save the user id as service_user_id, api_token.id as api_token_id, and the returned token as service_api_token. You need both IDs to remove the credentials later and associate them with audit events.
Step 1.5: Create the service user’s client certificate
The client authentication certificate must belong to the same service user whose API token you use with Software Trust Manager. Generate the private key locally, submit only the CSR, and authenticate the request with the service-user API token returned when you create the setup service user. Send the token in the x-api-key header. The API associates the new certificate with the authenticated service user and never receives the private key.
Set CLIENT_AUTH_CERTIFICATE_EXPIRATION to an approved UTC timestamp no more than 397 days in the future. The Account Manager API requires the expiration_date property.
umask 077
CLIENT_AUTH_CERTIFICATE_NAME="Private trust stack client authentication"
CLIENT_AUTH_CERTIFICATE_EXPIRATION="2027-01-31T23:59:59Z"
openssl req -new -newkey rsa:2048 -nodes \
-keyout client.key \
-out client.csr \
-subj "/CN=Private trust stack setup"
chmod 600 client.key client.csr
jq -n \
--rawfile csr client.csr \
--arg name "${CLIENT_AUTH_CERTIFICATE_NAME}" \
--arg expiration_date "${CLIENT_AUTH_CERTIFICATE_EXPIRATION}" '
{
csr: $csr,
name: $name,
expiration_date: $expiration_date
}' |
curl --fail-with-body --silent --show-error \
-X POST "https://demo.one.digicert.com/account/api/v1/client-auth-certificate" \
-H "x-api-key: ${SERVICE_API_TOKEN}" \
-H "Content-Type: application/json" \
--data-binary @- \
-o client-auth-certificate-response.json
jq -er '.x509_cert' client-auth-certificate-response.json > client.crt
jq -er '.ca_cert' client-auth-certificate-response.json > client-auth-ca.pem
jq -er '.id' client-auth-certificate-response.json > client-auth-certificate-id.txt
CLIENT_AUTH_CERTIFICATE_ID="$(<client-auth-certificate-id.txt)"
export CLIENT_AUTH_CERTIFICATE_ID
chmod 600 \
client.crt \
client.key \
client-auth-ca.pem \
client-auth-certificate-id.txt \
client-auth-certificate-response.json
Use the response end_date and the certificate’s notAfter value to determine the actual expiration. Your tenant’s API might adjust the requested expiration boundary instead of returning the exact expiration_date value. Confirm that the actual validity period complies with your credential policy before you use the certificate.
Confirm the certificate and private key match, validate the certificate against the returned CA certificate, and verify that mutual TLS (mTLS) authenticates the intended service user:
test "$(openssl x509 -in client.crt -pubkey -noout | openssl sha256)" = \
"$(openssl pkey -in client.key -pubout | openssl sha256)"
openssl verify -CAfile client-auth-ca.pem client.crt
jq '{end_date}' client-auth-certificate-response.json
openssl x509 -in client.crt -noout -enddate
curl --fail-with-body --silent --show-error \
--cert client.crt \
--key client.key \
"https://clientauth.demo.one.digicert.com/account/api/v1/user/me" |
jq -e --arg user_id "${SERVICE_USER_ID}" '
if .id == $user_id then true
else error("client certificate belongs to an unexpected user")
end' > /dev/null
Phase 1 checkpoint
-
account_ididentifies the intended active account. -
organization_ididentifies the approved active organization. - The service user has only the required tenant role names.
-
service_api_tokenis stored in a secret manager or protected local file and does not appear in logs. -
client.crtandclient.keybelong to the service user and match cryptographically. - The response
end_datematches the certificate’snotAftervalue and complies with the approved validity policy.