[Solved] credentialSecret, flows stopped as the credentials could not be decrypted

I'm going around and around in circles trying to solve this one, hoping someone out there has an answer.

I am bringing up customized docker containers running nodered with projects enabled and setting a custom credentialSecret in the settings.js file. Inside the container I have pre-cloned the repo and set the remote origin.

git --git-dir=data/projects/myproject/.git remote set-url origin https://git.com/myproject.git

When the container is up for the first time I am presented with the projects GUI and so I click to load an existing project. After entering my git username and email I select the project from the list and it loads. At this point I always get a red box with:

Flows stopped as the credentials could not be decrypted.
The flow credential file is encrypted, but the project's encryption key is missing or invalid.

... if I then go into the project settings (GUI) and set my encryption key to what I have in the settings.js file it works...? I even created a node that dumped the RED.settings to prove to myself that the strings matched. It does not matter if I am reading in from a docker secrets file, ENV var or even setting the string directly in the settings.js file.

image

.... maybe also worth noting, it's prompting me for a new key rather than a key change.

I'm running Node-red 1.03 with Node 12.13.1 on Aline Linux 3.10.3.

help :slight_smile:

If you have projects enabled then it will use the credentialSecret you have configured for the project, not the 'global' one in your settings file.

Now I know where to look I found the file where it's stored. I'll have to create a startup script to alter that json if I want to avoid sharing credentials.

Thanks for the pointer :+1:

Where are the project specific credentialSecret's stored? Readable?

Wouldn't it make sense to modify the mentioned paragraph in settings.js: By default and when not using projects, ..

Project settings are stored in ~/.node-red/.config.json

Circling back to this topic today, I'm hoping something like this can be achieved in the settings.js:

editorTheme: {
        projects: {
            enabled: true,
            activeProject: "myproject",
            projects: {
                myproject: {
                    credentialSecret: fs.readFileSync('/run/secrets/myproject', 'utf8').trim()
                }
            }
        }
    }

... this is so I can pre-build a container image with a project cloned into ~/data/projects. With this a container hosting a nodered flow will not come back flowless if it was to be scaled down and then back up again (or simply fail and restore itself).

Is there a way to do this currently / a workaround I could try?

If the docker image is intended to just run the flows - and not provide the editor etc - then the recommended pattern would be to build the container without the projects feature enabled, using the flow files etc from your project repository.

There's an outline of that pattern in this blog post - https://knolleary.net/2018/06/01/creating-a-node-red-deployment-pipeline-to-ibm-cloud/

In my use case I rely on the editor being there and the projects feature is an excellent way to essentially save a flow somewhere outside of the container. A flow may need a fast update in order to complete task due to some unforeseen circumstance and it must be running in a specific environment which is why it's containerized.

At the moment every time the container spins up I must manually go in and set up the project, the more of these instances I spin up the more unmanageable that's becoming. I'm trying to figure out the best solution, I could potentially set this up to load the flow.json and credentialSecret in the standalone manor so at least the flow is running when the container establishes itself. That seems messy though so I'm hoping to find a way to establish the on load project as it were.

Thanks again for your input!

I started down the path of updating our adminAuth module by adding a promise call that writes a file to ensure it's only executed at first login. The file will exist for as long as the container is alive.

I'm now trying to use the projects API to init the project with partial success. Basically I'm failing because I can't figure out the API call to set the credentialSecret. I only have the following options:

const proj = require("@node-red/runtime").projects;
proj.setActiveProject({user: username, id: "myproject"}) 

If I debug the browser when I manually enter the credentialSecret I see the admin API in action:

..../projects/myproject

{"files":{"package":"package.json","flow":"flows.json","credentials":"flows_cred.json"},"credentialSecret":"XXXXXXX"}

... I'm a little confused as to why the admin API would set the projects credentialSecret. Is it safe to assume that this sets both the active project and global credentialSecret or am I heading down a rabbit hole?

I continued on and got to a good working solution, I wanted to post in case anyone else found themselves doing something similar. loggedOnUser is the result from a successful login attempt (https://nodered.org/docs/user-guide/runtime/securing-node-red)

const proj = require("@node-red/runtime").projects;
const nrset = require("@node-red/runtime").settings;

function initContainer(username, loggedOnUser) {  
        if (fs.existsSync(initfile)) {    
            return loggedOnUser;
        } else {
            const u = { git: { user: { name: username, email: username + '@email.com' } } };
            const p = { credentialSecret: fs.readFileSync('/run/secrets/mysecret', 'utf8').trim() }
            return nrset.updateUserSettings({ user: { username: username }, settings: u })
                .catch((err) => { console.log("USER:INITFAIL:SETGIT:" + err) })
                .then(() => proj.setActiveProject({user: username, id: "myproject"}))
                .catch((err) => { console.log("USER:INIT:SETPROJECT:expect credentials to fail at first and --> " + err) })
                .then(() => proj.updateProject({user: username, id: "myproject", project: p}))
                .catch((err) => { console.log("USER:INITFAIL:UPDATEPROJECT:" + err) })
                .then(() => proj.setBranch({user: username, id: "myproject", branch: "master"}))
                .catch((err) => { console.log("USER:INITFAIL:SETBRANCH:" + err) })
                .then(() => proj.pull({user: username, id: "myproject", track: true, remote: "origin/master"}))
                .catch((err) => { console.log("USER:INITFAIL:PULL:" + err) })
                .then(() => { fs.closeSync(fs.openSync(initfile, 'a')) })
                .catch((err) => { console.log("USER:INITFAIL:WRITEINITFILE:" + err) })
                .then(() => { return loggedOnUser });
        }
    }
1 Like

That's a lot of work using unpublished internal APIs that could change in the future, where you could just be writing a few lines to .config.json and be done with it.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.