Good way to make sure my gateway scripts only run on the server?

More of a design question than anything. I have gateway scripts, some of which involve emailing our companies clients. On occasion, I’ve had coworkers unknowingly backup the gateway to a development machine with an old database copy, and then all of a sudden our clients are getting emails with outdated information that they already received. Not great.

Currently I’ve been telling people to not export/import gateway scripts, but there are other scripts that don’t involve emails that are essential to the functioning of the application. What would be the best/most modular way to make it so I/my coworkers can export a copy of my production gateway to a development VM or my own localhost, and not have to worry about accidentally running gateway scripts?

Consider placing a JSON encoded file in your production gateway’s filesystem containing configuration values for environment-dependent items. Place an alternate version in each development server. Load it in the top level of a project script at the root of your inheritance tree, so that it can be accessed trivially where needed, something like myGlobalScript.myConfigJson.someItem. Anything that should only run in Production, or do something specific differently, would check that JSON object for the decision.

The chosen filesystem location must be outside any source-control scope.

4 Likes

Ok I think this is sort of what I saw in a Flask tutorial, so I think I understand what you’re saying, correct me if I’m wrong. I’d have a script in a module myConfigJson

def runOnGateway():
    """
    Determines if a script should run on the gateway.
    """
    import OS
   try:
       # Some code here to grab the json object from the file
       return json[someItem]
   except OSError:
       # For when the file does not exist
       return False

Then in my gateway scripts

import myConfigJson

if myConfigJson.runOnGateway():
    # rest of my script here

Where my json file would be something like

config = {
    runOnGateway = True,
}

I did see some other project someone did using IP’s but I really dislike hardcoding IP addresses into code. I like your idea of a config file.

You could also rely on environment variables that you access via System.getenv(). It’s the same idea as the JSON config file and the same way many web applications are deployed - certain configuration details are pulled from the environment rather than from code or configuration files that are part of the application.

Ah ok, how would I get access to this through scripting? Also, I see there’s no setEnv - how would I set the environment variable? I can’t tell from the documentation. Also, how would I actually use this via scripting?

import system
system.getEnv()

does not work.

Something like this:

import java.lang.System

prod = System.getenv().get("IGNITION_PROD")

These environment variables are read-only, you wouldn’t set them from Ignition, which is kind of the point.

edit: looks like System.getenv("IGNITION_PROD") also works.

Ok I got that to work, howver running

import java.lang.System as System

prod = System.getenv().get("IGNITION_PROD")

print str(prod)

on both my development designer and my production designer consoles, prod is None in both instances. I do see there are other options in getenv() I can probably use though, but what is “IGNITION_PROD” supposed to represent? It seems odd its None on my unlicensed development designer and my licensed/activated production designer.

I’d probably not bother with the function. Just access the json object directly. The custom module would have something like this:

import json

with open("/path/to/my/json/file.json", "r") as f:
    myConfigJson = json.load(f)

Then in your other scripts, all you’d do is:

if myGlobalScript.myConfigJson['runOnGateway']:
    # do something on production gateway
else:
    # fake it on a dev gateway
1 Like

That’s an environment variable name that I made up for the purpose of this example. It could be whatever you want it to be. You have to set it in your actual production environment if you want it to be available (which, again, is the point).

1 Like

If you are going the environment variable route, you might find python’s os.environ dictionary to be most convenient.
In module:

import os

runOnGateway = bool(os.environ['SOME_ENV_VARIABLE'])

In other scripts:

if myGlobalScript.runOnGateway:
    # do something on production gateway
else:
    # fake it on a dev gateway

Note that environment variables are really bad places to put anything private. If you need different credentials for production versus dev, use the JSON file approach.

Putting secrets in environment variables is a generally accepted practice because usually your application is running on a server where users can't arbitrarily run scripts or read from the env.

But... Ignition is not such an application. Anybody with designer access can read the env variables. And fwiw, anybody with designer access can read your JSON file too, so it's only marginally more secure via obscurity.

(anybody with designer access should be trusted)

Uh, no. Heck, no. A sample of contrary opinions:

Even when Microsoft is recommending storing secrets in environment variables instead of in an app's code itself, they have a big fat warning, and then describe how to use their JSON infrastructure instead:

So, no, just no. No secrets in the environment.

This, yes. Any designer, in the hands of a malicious user, can take total control of a gateway in spite of role-based security.

1 Like

I am trying to get better at understanding the security aspect of Ignition so someone please correct me if im wrong -

To summarize, if someone has designer access, they’d easily be able to see my environment variables with the system.getEnv() script right? But unless they had access to the server itself they could not change that.

Where as the config.json method, they would have to search my scripts to find it. But if they could find it, would they be able to edit it via designer? Couldn’t they open it up, read it, edit it, and resave it with a script?

If they crack Ignition itself, or otherwise gain access as the Ignition service user, yes. They’d be able to read that file. As another user, no, you can set the security on the file to prevent other users access. Similar to what is done to protect SSL private keys.
If they gain root access, all bets are off. Labeling files for SELinux can help even that case, though.

1 Like

Ah ok that was the piece missing in my head, you would have set security on the file to not allow other users to edit. Thanks

You could prevent even Ignition from writing back to the JSON file by letting root own it, and set its group to something that only Ignition belongs to, and let the group have read-only access.

As for manipulating environment variables, at least in java memory, you might enjoy all the hacks in this StackOverflow post:

1 Like

Hey, just wanted to give an update, it seems while running the script to get environment variables on a designer running on the gateway works, trying to run the

import java.lang.System as System

prod = System.getenv().get("IGNITION_PROD")

print str(prod)

on a gateway script always gives me a None even though there is an environment variable.

Where are you setting this environment variable? In modern Linux, systemd units are started with sanitized environments. You have to define additional variables there. In windows, services will see only system environment variable, IIRC.

{Not that I’m suggesting this… I still think @Kevin.Herron is wrong… (: }

I take it back. I accidentally added to the variables for Administrator at the top
instead of System Variables at the bottom. Works fine now. Was just developer induced error.
image

1 Like