Issue Injecting OAuth Secrets (client_id/client_secret) into IRIS Productions in Docker
Hello Community,
I’m working on an InterSystems IRIS production that needs to call an external API using OAuth client credentials (client_id and client_secret). For security reasons, I must pass these credentials via environment variables in my Docker container.
In the IRIS terminal, I can successfully retrieve these environment variables using $System.Util.GetEnviron("api-clientid") and $System.Util.GetEnviron("api-clientsecret"). However, inside my Business Operation class (OnMessage method), these environment variables return empty strings.
I verified that the variables are properly set in the Docker container environment, and the IRIS instance has access to them in the terminal session.
I suspect that the production or namespace context might not have access to the environment variables, or that the way IRIS runs productions might isolate the environment variables.
I have also read about potentially injecting variables into the namespace environment via system configurations, but I’m unsure how to do this effectively or if it will solve the problem.
Questions:
- How can I reliably access environment variables from within an IRIS production running inside Docker or Kubernetes?
- Is there a recommended best practice for injecting secrets such as client_id and client_secret into the IRIS environment used by productions?
- Any examples or configurations that have worked for others to pass sensitive environment variables into Business Operations or Services?
Thanks in advance for your help!
Comments
Hi @TAZ.R
Environment variables set in the Docker container are only visible to processes that inherit the environment.
You can rapidly check it from a terminal session :
write$system.Util.GetEnviron("TZ")
Europe/Paris
write##class(%SYS.Python).Import("os").getenv("TZ")
Europe/Paris
Or from a simple Business Operation :
.png)
BusinessOperation env
Class operations.env Extends Ens.BusinessOperation
{
Method env(pRequest As Ens.StringContainer, Output pResponse As Ens.StringContainer) As%Status
{
set sc=$$$OKTry {
set env = pRequest.StringValue
set value = $system.Util.GetEnviron(env)
set pResponse = ##class(Ens.StringContainer).%New()
set pResponse.StringValue = value
$$$LOGINFO("Environment: "_env _":"_value)
}
Catch ex {
Set tSC=ex.AsStatus()
$$$LOGERROR("ERROR:" _ $system.Status.GetErrorText(tSC))
set sc = tSC
}
return sc
}
Method pyenv(pRequest As Ens.StringRequest, Output pResponse As Ens.StringContainer) As%Status
{
set sc=$$$OKTry {
set env = pRequest.StringValue
// Use Python to get the environment variableset value = ##class(%SYS.Python).Import("os").getenv(env)
// Call the Python method to get the environment variable
#; set value = ..getEnv(env) ; Uncomment this line if you want to use the Python method directlyset pResponse = ##class(Ens.StringContainer).%New()
set pResponse.StringValue = value
$$$LOGINFO("Environment: "_env _":"_value)
}
Catch ex {
Set tSC=ex.AsStatus()
$$$LOGERROR("ERROR:" _ $system.Status.GetErrorText(tSC))
set sc = tSC
}
return sc
}
Method getEnv(env As%String) As%String [ Language = python ]
{
import os
value = os.getenv(env)
return value
}
XData MessageMap
{
<MapItems>
<MapItem MessageType="Ens.StringContainer">
<Method>env</Method>
</MapItem>
<MapItem MessageType="Ens.StringRequest">
<Method>pyenv</Method>
</MapItem>
</MapItems>
}
}
You could also store the content in a global at IRIS startup by creating the %ZSTART routine
ROUTINE %ZSTARTQUIT; Prevents direct execution without a label
SYSTEM ; Subroutine called at system startupSet^IRIS.Temp("Secrets", $System.Util.GetEnviron("API_CLIENT_ID")) = $System.Util.GetEnviron("API_CLIENT_SECRET")
QUITThat way, you can retrieve your secret by executing this code:
$Get(^IRIS.Temp("Secrets", "your-client-id"), "")Business Operation secret
Class operations.secrets Extends Ens.BusinessOperation
{
Method secret(pRequest As Ens.StringContainer, Output pResponse As Ens.StringContainer) As%Status
{
set sc=$$$OKTry {
set env = pRequest.StringValue
set value = $Get(^IRIS.Temp("Secrets", env), "")
set pResponse = ##class(Ens.StringContainer).%New()
set pResponse.StringValue = value
$$$LOGINFO("Environment: "_env _":"_value)
}
Catch ex {
Set tSC=ex.AsStatus()
$$$LOGERROR("ERROR:" _ $system.Status.GetErrorText(tSC))
set sc = tSC
}
return sc
}
XData MessageMap
{
<MapItems>
<MapItem MessageType="Ens.StringContainer">
<Method>secret</Method>
</MapItem>
</MapItems>
}
}
Hi @Sylvain Guilbaud,
Thank you for your reply.
Basically you're saying that I need to store the secrets in a global at IRIS startup using a %ZSTART routine so I can retrieve them in a business operation, am I right ?
I have tried to do so with this routine :
ROUTINE %ZSTARTQUIT;
SYSTEM
Set^IRIS.Temp("Secrets", "APIclientid") = $System.Util.GetEnviron("APIclientid")
Set^IRIS.Temp("Secrets", "APIclientsecret") = $System.Util.GetEnviron("APIclientsecret")
QUITI have loaded it in the %SYS namespace and I can check that it has been successfuly imported when I go on the Management Portal -> System Explorer -> Routines.
Then, I set my credentials in my business operation this way to use then in a HTTP request :
Set APIclientid = $Get(^IRIS.Temp("Secrets", "APIclientid "),"")
Set APIclientsecret = $Get(^IRIS.Temp("Secrets", "APIclientsecret"),"")However, when I want to test my production, it does not seem to work, APIclientID and APIclientsecret still return empty values (while $System.Util.GetEnviron("APIclientid") returns the expected value)
Am I doing something wrong ?
Kind regards,
Hi Tanguy,
%ZSTART routine is not mandatory. It's just an example of a way to retrieve and store data at instance startup. If it works you should see the results in System > Globals > View Global Data > ^IRIS.Temp global.
If $System.Util.GetEnviron("APIclientid") works, it means that your environment variables are well configured.
Could you please share your business operation code ?
Hi Sylvain.
In System > Globals > View Global Data > ^IRIS.Temp global I can see :
| 1: | ^IRIS.Temp("Secrets","clientid") | = | "" |
| 2: | ^IRIS.Temp("Secrets","clientsecret") | = | "" |
The %ZSTART seems to work but not retrieving the values.
You will find just below the Business Operation that tries to use the clientid and clientsecret :
Class HTTP.PostOperation.GetToken Extends Ens.BusinessOperation
{
Parameter ADAPTER = "EnsLib.HTTP.OutboundAdapter";Parameter SETTINGS = "clientid, clientsecret, databasereference, audience, granttype, server";// Paramètres configurables depuis la production/// Adresse du serveur cible Property server As%String;/// Identification du credential clientid à utiliser pour faire la requête vers l'APIProperty clientid As%String;/// Identification du credential clientsecret à utiliser permettant la requête vers l'APIProperty clientsecret As%String;/// audience permettant la requête vers l'APIProperty audience As%String;/// référence de la database associée à l'établissement de santé concernéProperty databasereference As%String;/// granttype permettant la requête vers l'APIProperty granttype As%String [ InitialExpression = "client_credentials" ];
Method OnMessage(pRequest As HTTP.request, Output pResponse As HTTP.response) As%Status
{
Set pResponse = ##class(HTTP.response).%New()
/// 1. Purge des anciens tokens expirésDo##class(INTEROP1.TokenCache).PurgeExpired()
/// 2. Vérifie si un token valide est déjà en cacheSet token = ""Set success = ##class(INTEROP1.TokenCache).GetValidToken("TokenCache", .token)
If success {
Write"Token récupéré depuis le cache", !
Set pResponse.Token = token
Set pResponse.Log = "Token récupéré depuis le cache"Quit$$$OK
}
Write"Aucun token valide trouvé, appel HTTP en cours...", !
/// 3. Appel HTTP POST pour récupérer un nouveau tokenSet httpRequest = ##class(%Net.HttpRequest).%New()
Set httpRequest.Server = ..serverSet httpRequest.Https = 1Set httpRequest.SSLConfiguration = "default"Set httpRequest.ContentType = "application/json"// Construction du corps de la requêteSet jsonObject = ##class(%DynamicObject).%New()
/// Si injection env. variable Set clientid = $Get(^IRIS.Temp("Secrets", "APIclientid"),"")
Set clientsecret = $Get(^IRIS.Temp("Secrets", "APIclientsecret"),"")
/// Set clientid = $System.Util.GetEnviron("APIclientid") /// Set clientsecret = $System.Util.GetEnviron("APIclientsecret") Do jsonObject.%Set("client_id", clientid)
Do jsonObject.%Set("client_secret", clientsecret)
Do jsonObject.%Set("audience", ..audience)
Do jsonObject.%Set("grant_type", ..granttype)
Do jsonObject.%Set("database_reference", ..databasereference)
Set requestPayload = jsonObject.%ToJSON()
Do httpRequest.EntityBody.Write(requestPayload)
Set status = httpRequest.Post("//v1/token")
If$$$ISERR(status) {
Write"Erreur durant l'appel HTTP", !
Quit status
}
/// 4. Lecture de la réponseSet rawJSON = httpRequest.HttpResponse.Data.Read()
Set jsondyn = ##class(%DynamicObject).%FromJSON(rawJSON)
Set token = jsondyn.%Get("access_token")
Set expiresIn = jsondyn.%Get("expires_in")
Set pResponse.RawJSON = rawJSON
Set pResponse.json = jsondyn
Set pResponse.Token = token
/// 5. Enregistre le token dans la base avec expirationDo##class(INTEROP1.TokenCache).SaveToken("TokenCache", token, expiresIn)
If pResponse.Token '= ""Write"Nouveau token stocké dans le cache", !
Set pResponse.Log = "Nouveau token stocké dans le cache"If pResponse.Token = ""Write"Impossible de récupérer le token", !
Set pResponse.Log = "Impossible de récupérer le token"Quit$$$OK
}
}FYI, I have also tried this in the terminal and it does not seem to work :
(Before doing so, I tried to add a new line in the %ZSTART :
Set^IRIS.Temp("Secrets","TEST") = "Hello"%SYS>do^%ZSTART%SYS>zw^IRIS.Temp("Secrets")
^IRIS.Temp("Secrets","TEST")="Hello"^IRIS.Temp("Secrets","lifenclientid")=""^IRIS.Temp("Secrets","lifenclientsecret")=""The environment variables have been setup in my docker container and can be found in the docker-compose.yml :
....
Env": [
"APIclientid=testclientid",
"APIclientsecret=testclientsecret",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/irisowner/bin",
"ISC_PACKAGE_IRISGROUP=irisowner",
"ISC_PACKAGE_IRISUSER=irisowner",
"ISC_PACKAGE_MGRGROUP=irisowner",
"ISC_PACKAGE_MGRUSER=irisowner",
"IRISSYS=/home/irisowner/irissys",
"TINI_VERSION=v0.19.0",
"ISC_PACKAGE_INSTANCENAME=IRIS",
"ISC_PACKAGE_INSTALLDIR=/usr/irissys",
"PYTHONPATH=/usr/irissys/mgr/python",
"LANG=en_US.UTF-8",
"LANGUAGE=en_US.UTF-8",
"LC_ALL=en_US.UTF-8"
],
....Maybe I have not correctly setup the environment variable and this is why this is not working ?
Kind regards,
Hi Tanguy,
in your environment variables I can see APIclientid and APIclientsecret but nothing called lifenclientid nor lifenclientsecret.
That's probably why you don't get any value.
Tanguy,
you can find a full example of retrieving environment variables from a Business Operation either from a global or from $system.Util.GetEnviron(env) in this repo:
https://github.com/SylvainGuilbaud/iris-dev
a really helpful repo, @Sylvain Guilbaud ! do you want to publish it on OEX? Please? )
Hi @Sylvain Guilbaud
Actually that is just a small typo, I have forgotten to replace it with APIclientid and APIclientsecret in this community post... 😁
I will look into your example right now.
Kind regards
@Sylvain Guilbaud
I decided to deploy a new container and set up new environment variables.
I have then setup my %ZSTART and it is now working.
I don't really understand why.
The only difference is that I have changed the variable environment names...
Thank you for your help
Hi Tanguy,
happy to hear you've finally fixed it.
Again, %ZSTART is not at all mandatory ; as soon as your ENV variables are available in your container you should be able to get it either from $system.Util.GetEnviron(env) or ##class(%SYS.Python).Import("os").getenv(env)
With these variables :
{"Env": [
"API_CLIENT_SECRET=ea5663885513e5b00df120fa4b4da8e1150398cde9d41ee27b5a8c6f1898dfa63ae711b82bf06b36475b646453a9092f5653895ddd2c3bb067d9a4f562a6b625",
"TZ=Europe/Paris",
"ISC_DATA_DIRECTORY=/irisdata",
"ISC_CPF_MERGE_FILE=/app/merge.cpf",
"API_CLIENT_ID=3e726f42daaa06a8",
"ISC_PACKAGE_IRISGROUP=irisowner",
"ISC_PACKAGE_IRISUSER=irisowner",
"ISC_PACKAGE_MGRGROUP=irisowner",
"ISC_PACKAGE_MGRUSER=irisowner",
"IRISSYS=/home/irisowner/irissys",
"TINI_VERSION=v0.19.0",
"ISC_PACKAGE_INSTANCENAME=IRIS",
"ISC_PACKAGE_INSTALLDIR=/usr/irissys",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/irisowner/bin",
"PYTHONPATH=/usr/irissys/mgr/python",
"LANG=en_US.UTF-8",
"LANGUAGE=en_US.UTF-8",
"LC_ALL=en_US.UTF-8"
]}You should get :
write$SYSTEM.Util.GetEnviron("API_CLIENT_ID")
3e726f42daaa06a8
write$SYSTEM.Util.GetEnviron("API_CLIENT_SECRET")
ea5663885513e5b00df120fa4b4da8e1150398cde9d41ee27b5a8c6f1898dfa63ae711b82bf06b36475b646453a9092f5653895ddd2c3bb067d9a4f562a6b625
write$SYSTEM.Util.GetEnviron("TZ")
Europe/Paris
write##class(%SYS.Python).Import("os").getenv("API_CLIENT_ID")
3e726f42daaa06a8
write##class(%SYS.Python).Import("os").getenv("API_CLIENT_SECRET")
ea5663885513e5b00df120fa4b4da8e1150398cde9d41ee27b5a8c6f1898dfa63ae711b82bf06b36475b646453a9092f5653895ddd2c3bb067d9a4f562a6b625
write##class(%SYS.Python).Import("os").getenv("TZ")
Europe/ParisOr, with %ZSTART containing :
ROUTINE %ZSTARTQUIT; Prevents direct execution without a label
SYSTEM ; Subroutine called at system startup; Initialize the IRIS application secretsSet^Secrets("API_CLIENT_ID") = $System.Util.GetEnviron("API_CLIENT_ID")
Set^Secrets("API_CLIENT_ID","LastUpdated") = $ZDATETIME($HOROLOG,3)
Set^Secrets("API_CLIENT_SECRET") = $System.Util.GetEnviron("API_CLIENT_SECRET")
Set^Secrets("API_CLIENT_SECRET","LastUpdated") = $ZDATETIME($HOROLOG,3)
QUITyou should obtain :
IRISAPP>zw^Secrets^Secrets("API_CLIENT_ID")="3e726f42daaa06a8"^Secrets("API_CLIENT_ID","LastUpdated")="2025-08-07 17:16:19"^Secrets("API_CLIENT_SECRET")="ea5663885513e5b00df120fa4b4da8e1150398cde9d41ee27b5a8c6f1898dfa63ae711b82bf06b36475b646453a9092f5653895ddd2c3bb067d9a4f562a6b625"^Secrets("API_CLIENT_SECRET","LastUpdated")="2025-08-07 17:16:19"
IRISAPP>d SYSTEM^%ZSTART
IRISAPP>zw^Secrets^Secrets("API_CLIENT_ID")="3e726f42daaa06a8"^Secrets("API_CLIENT_ID","LastUpdated")="2025-08-07 17:24:02"^Secrets("API_CLIENT_SECRET")="ea5663885513e5b00df120fa4b4da8e1150398cde9d41ee27b5a8c6f1898dfa63ae711b82bf06b36475b646453a9092f5653895ddd2c3bb067d9a4f562a6b625"^Secrets("API_CLIENT_SECRET","LastUpdated")="2025-08-07 17:24:02"@Sylvain Guilbaud,
Yes indeed. With this new instance, the SYSTEM.Util.GetEnviron("API_CLIENT_ID") works within a Business Operation.
The environment variable is now available for every business operation.
I don't even need to use %ZSTART.
I hope I will one day understand why it works now!
Thank you for your help.
It works because you published a question on Developer Community :) (Joking, of course :)
Jokes aside - a very helpful discussion! Thanks @Sylvain Guilbaud !
.png)