Question
· Jul 16

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!

Product version: IRIS 2023.3
Discussion (11)4
Log in or sign up to continue

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 :

 
BusinessOperation env

You could also store the content in a global at IRIS startup by creating the %ZSTART routine 

ROUTINE %ZSTART
  QUIT ; Prevents direct execution without a label

SYSTEM ; Subroutine called at system startup
  Set ^IRIS.Temp("Secrets", $System.Util.GetEnviron("API_CLIENT_ID")) = $System.Util.GetEnviron("API_CLIENT_SECRET")
  QUIT
  

That way, you can retrieve your secret by executing this code:

$Get(^IRIS.Temp("Secrets", "your-client-id"), "")
 
Business Operation secret

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 %ZSTART

  QUIT ; 

SYSTEM
  Set ^IRIS.Temp("Secrets", "APIclientid") = $System.Util.GetEnviron("APIclientid")
  Set ^IRIS.Temp("Secrets", "APIclientsecret") = $System.Util.GetEnviron("APIclientsecret")

  QUIT

I 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'API
    Property clientid As %String;
    /// Identification du credential clientsecret à utiliser permettant la requête vers l'API
    Property clientsecret As %String;
    /// audience permettant la requête vers l'API
    Property 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'API
    Property 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és
        Do ##class(INTEROP1.TokenCache).PurgeExpired()

        /// 2. Vérifie si un token valide est déjà en cache
        Set 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 token
        Set httpRequest = ##class(%Net.HttpRequest).%New()
        Set httpRequest.Server = ..server
        Set httpRequest.Https = 1
        Set httpRequest.SSLConfiguration = "default"
        Set httpRequest.ContentType = "application/json"
        // Construction du corps de la requête
        Set 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éponse
        Set 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 expiration
        Do ##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,

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/Paris

Or, with %ZSTART containing :
 

ROUTINE %ZSTART
  QUIT ; Prevents direct execution without a label

SYSTEM ; Subroutine called at system startup
  ; Initialize the IRIS application secrets
  Set ^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)
  QUIT

you 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"