Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Credential Decryption fails on local Project open #2868

Open
5 tasks
promd opened this issue Feb 9, 2021 · 12 comments
Open
5 tasks

Credential Decryption fails on local Project open #2868

promd opened this issue Feb 9, 2021 · 12 comments

Comments

@promd
Copy link

promd commented Feb 9, 2021

Scenario: Trying to prepare a deployable image using NodeRed Projects. Not all consumers of the image have access to the Project repositories.

What are the steps to reproduce?

  • clone a project repo into the "projects" folder (on shell)
  • goto NodeRed -> Projects -> New
  • select Open -> Select the project cloned into the directory.

What happens?

  • Project fails to open with message "Flows stopped as the credentials could not be decrypted."
  • Trying to set the decryption key does not work. ("Setup Credentials" -> "edit")

What do you expect to happen?

  • either the "open existing project" dialog should show a entry field for the decryption key, or
  • at least setting the key in "Setup Credentials" -> "edit" should work

Please tell us about your environment:

  • Node-RED version: v1.2.9
  • Node.js version: (latest Docker Image used)
  • npm version: (latest Docker Image used)
  • Platform/OS: RPI4 (latest Docker Image used)
  • Browser: Chrome
@knolleary
Copy link
Member

When I try to recreate this, whilst the Open Existing Project dialog could handle the scenario better, I am able to open up the project settings dialog and set the credentials key to get it working.

Will experiment some more, but as is stands, I can't recreate everything you are reporting.

I will also look at improving the workflow of opening a project that has been manually cloned, rather than cloned by Node-RED.

@promd
Copy link
Author

promd commented Feb 16, 2021

Strange... did you try on a fresh NodeRed install ?
In my case, I am re-building the docker host to test it's setup process, so the NodeRed I am using is completely fresh.
Not sure if important, but I am starting it with a customized settings.js that enables Projects and HTTPS.

Many Thanks for looking into it - much appreciated!

@promd
Copy link
Author

promd commented Feb 16, 2021

here's the clone process:
image
and how Node-Red reacts after "open existing project":
image

When I open the credential settings, I can enter the credentials - but hitting save will only get me back to the same screen.
image

@truongminh
Copy link

truongminh commented Feb 23, 2021

The secret is generated and saved in the config.json file.
In a fresh node-red setup, the config file is missing.

The issue can be solved by either:

  • Put the right config file in the new setup
  • Override the settings with the correct credentialSecret, then call RED.init(server, settings);.

@knolleary
Copy link
Member

@truongminh that is not the case for Projects.

The credentialSecret used by projects is provided by the user - not generated - and is stored in .config.projects.json.

@promd where you say:

When I open the credential settings, I can enter the credentials - but hitting save will only get me back to the same screen.

I have pushed a fix for that part - 85e05b7 - so the Open Project dialog will close and you can click the 'Setup credentials' link in the notification.

Where you say:

When I open the credential settings, I can enter the credentials - but hitting save will only get me back to the same screen.

Which 'same screen' are you referring to? I still haven't been able to recreate this path - for me, setting the correct credential key works.

@echoix
Copy link

echoix commented Apr 5, 2021

I think I have some details about the OP's "same screen". I got a similar problem when I tried to edit the encryption key in the project settings. When I try to edit, by filling out the existing key (as typed, and as found in the .config.projects.json file), the following error pops-up:

An unexpected error occurred:
Cannot change credentialSecret without current key
code: missing_current_credential_key

image

I looked with the Chrome debugger what was happening, and if a recent change in node-red/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js has broken something, but this part didn't change in the last 4 years.

if (credentialSecretResetButton.hasClass('selected') || credentialSecretEditButton.hasClass('selected')) {
payload.credentialSecret = credentialSecretNewInput.val();
if (credentialSecretExistingInput.is(":visible")) {
payload.currentCredentialSecret = credentialSecretExistingInput.val();
}
}

I'm not quite sure that I have the good elements selected, but I tried to go to the element when clicking the O variable in the Chrome debugger (red.min.js, version 1.2.9, O is credentialSecretExistingInput), and getting the selector. I think that whatever the case is, the .is(":visible") will return false. Furthermore, I get the same results with the P variable (red.min.js, version 1.2.9, P is credentialSecretNewInput), that is, .is(":visible") false, in the reset and edit credentials mode, and even before clicking save.

> $("#red-ui-project-settings-tab-settings > div:nth-child(2) > div:nth-child(4) > span.uneditable-input > div > div:nth-child(4) > input").is(":visible")
false

> $("input.red-ui-typedInput.input-error").is(":visible")
false

> $(P).is(":visible")
false

> $(O).is(":visible")
false

image

image

Is checking for the visibility of an input the good way to go in order to know if a user wants to edit or reset the key? In the HTML sample down below, the inputs right after the "Current key" and "New key" are always hidden. Is it this property that the .is(":visible") checks?

<label for="">Current key</label><input type="hidden" class="red-ui-typedInput" value="" />
<label for="">New key</label><input type="hidden" class="red-ui-typedInput" value="" />
<div class="red-ui-settings-row">
    <label></label>
    <span style="color: rgb(102, 102, 102); height: auto;" class="uneditable-input">
        <i class="user-settings-credentials-state-icon fa fa-lock"></i> <span class="user-settings-credentials-state">Encryption enabled</span>
        <div style="margin-top: 10px;">
            <div style="margin: 20px 0px 10px 5px; display: none;">Set the encryption key</div>
            <div style="margin: 20px 0px 10px 5px; display: none;">Change the encryption key</div>
            <div style="margin: 20px 0px 10px 5px;">Reset the encryption key</div>
            <div class="red-ui-settings-row red-ui-settings-row-credentials" style="display: none;">
                <label for="">Current key</label><input type="hidden" class="red-ui-typedInput" value="" />
                <div class="red-ui-typedInput-container">
                    <button class="red-ui-typedInput-type-select disabled" tabindex="0">
                        <i class="red-ui-typedInput-icon fa fa-caret-down" style="display: none;"></i>
                        <span class="red-ui-typedInput-type-label"><i class="red-ui-typedInput-icon fa fa-lock" style="min-width: 13px; margin-right: 4px;"></i></span>
                    </button>
                    <div class="red-ui-typedInput-input-wrap" style=""><input class="red-ui-typedInput-input" type="password" style="margin-right: 0px; margin-left: 0px;" /></div>
                    <div class="red-ui-typedInput-value-label" style="flex-grow: 0; pointer-events: none; background: none;">
                        <div style="position: absolute; right: 6px; top: 6px; pointer-events: all;">
                            <button type="button" class="red-ui-button red-ui-button-small" style="width: 20px;"><i class="fa fa-eye" style="margin-left: -2px;"></i></button>
                        </div>
                    </div>
                    <input type="hidden" value="cred" />
                    <button tabindex="0" class="red-ui-typedInput-option-trigger" style="display: none;">
                        <span class="red-ui-typedInput-option-label"></span><span class="red-ui-typedInput-option-caret"><i class="red-ui-typedInput-icon fa fa-caret-down"></i></span>
                    </button>
                    <button tabindex="0" class="red-ui-typedInput-option-expand" style="display: none;"><i class="red-ui-typedInput-icon fa fa-ellipsis-h"></i></button>
                </div>
            </div>
            <div class="red-ui-settings-row red-ui-settings-row-credentials">
                <label for="">New key</label><input type="hidden" class="red-ui-typedInput" value="" />
                <div class="red-ui-typedInput-container">
                    <button class="red-ui-typedInput-type-select disabled" tabindex="0">
                        <i class="red-ui-typedInput-icon fa fa-caret-down" style="display: none;"></i>
                        <span class="red-ui-typedInput-type-label"><i class="red-ui-typedInput-icon fa fa-lock" style="min-width: 13px; margin-right: 4px;"></i></span>
                    </button>
                    <div class="red-ui-typedInput-input-wrap" style=""><input class="red-ui-typedInput-input" type="password" style="margin-right: 0px; margin-left: 0px;" /></div>
                    <div class="red-ui-typedInput-value-label" style="flex-grow: 0; pointer-events: none; background: none;">
                        <div style="position: absolute; right: 6px; top: 6px; pointer-events: all;">
                            <button type="button" class="red-ui-button red-ui-button-small" style="width: 20px;"><i class="fa fa-eye" style="margin-left: -2px;"></i></button>
                        </div>
                    </div>
                    <input type="hidden" value="cred" />
                    <button tabindex="0" class="red-ui-typedInput-option-trigger" style="display: none;">
                        <span class="red-ui-typedInput-option-label"></span><span class="red-ui-typedInput-option-caret"><i class="red-ui-typedInput-icon fa fa-caret-down"></i></span>
                    </button>
                    <button tabindex="0" class="red-ui-typedInput-option-expand" style="display: none;"><i class="red-ui-typedInput-icon fa fa-ellipsis-h"></i></button>
                </div>
            </div>
            <div class="form-tips form-warning" style="margin: 10px;"><i class="fa fa-warning"></i>This will delete all existing credentials</div>
        </div>
    </span>
    <span class="button-group" style="margin-left: -72px; vertical-align: top;">
        <button type="button" class="red-ui-button selected" style="vertical-align: top; width: 36px; margin-bottom: 10px;"><i class="fa fa-trash-o"></i></button>
        <button type="button" class="red-ui-button" style="border-top-right-radius: 4px; border-bottom-right-radius: 4px; vertical-align: top; width: 36px; margin-bottom: 10px;"><i class="fa fa-pencil"></i></button>
    </span>
</div>

I'm not familiar enough to know how to fix, but I tried my best to locate and understand the bug.

@echoix
Copy link

echoix commented Apr 9, 2021

I tried different releases and specific commits, and found that this is a regression introduced at commit 1cd10f0. Releases 1.2.1 to 1.2.3 and commit bed1d31 were ok, release 1.2.5 to 1.3.1 are broken, i.e. can't change the project credential key (release 1.2.4 didn't really exist).

The commit that introduced the regression, 1cd10f0, enabled the TypedInput-cred input for the projectSettings.js, but the root cause should be in the TypedInput-cred implementation or style, not directly in the projectSettings.js.

If I could get some help with the good way to go replace the following condition, I could create a PR for it. For now, I still didn't figure out what should be checked instead.

if (credentialSecretResetButton.hasClass('selected') || credentialSecretEditButton.hasClass('selected')) {
payload.credentialSecret = credentialSecretNewInput.val();
if (credentialSecretExistingInput.is(":visible")) {
payload.currentCredentialSecret = credentialSecretExistingInput.val();
}
}

knolleary added a commit that referenced this issue Apr 19, 2021
@knolleary
Copy link
Member

@echoix I've pushed a fix for the regression you had identified. Rather than check the visibility of the input (which, in the case of a typedInput, is always hidden), we can check the visibility of the row it is in.

@echoix
Copy link

echoix commented Apr 21, 2021

I confirm that release 1.3.3 fixes the issue and allows to change the credentials (on docker, with node 12). Thanks!
I just want to make sure that this is the expected behaviour. If we enter the old and new credentials and save, Node-RED saves correctly and closes the editing mode. When we reopen the edit mode (without clicking close of the settings panel), the current and new key credentials are already populated, and if set to visible before, are visible. Most software we use don't use this behaviour, and I expected the field to be blank (fresh clean) when I reopen edit credentials.

  1. Open the projects setting, press the small edit button, and click the edit button to change the current credential current_key_cred to current_key_cred2. I pressed on the "eye" button to show the credential for the screenshot.
    image

  2. Save the changes, no errors are reported, we are brought back, out of the edit mode.
    image

  3. Let's reclick on the edit, to change the credentials again. We see that the fields are already populated, with the same visibility level.
    image

  4. Lastly, by changing to the reset (delete) credential, the new key is populated with the new credential from the edit tab, which is expected.
    image

  5. If we close the projects settings panel:
    image

  6. the existing and new credentials are blank as expected.
    image

@hyamanieu
Copy link

hyamanieu commented Aug 12, 2022

Following this discussion, it seems the problem was solved a year ago although still open.

I am using version v3.0.2 and this issue is still opened. Upon starting a fresh node-red docker with a pre-cloned project, I get prompted as others to createa new project, clone a project, or open an existing project (hiden at the bottom of the pop-up). I do the latter. Flows don't start as it never asks for the credential secret.

I still get this modular window:
image

Exactly like shown for v1.2.9, see following images:

image

After clicking edit:

image

Then saving...

image

The only solution is to do add in my container .config.projects.json , having a secret in clear text...

Proposed solutions/corrections:

  1. When opening an existing project, there should be a "credential secret" input exactly like for the "clone a project" page. If there is no secret, the user can simply leave it empty.
  2. possibility to add it directly in the setings.js, e.g.:
editorTheme: {
       projects: {
           enabled: process.env.NODE_RED_ENABLE_PROJECTS || true,
           default_project: process.env.NODE_RED_DEFAULT_PROJECT,
           default_url: process.env.NODE_RED_DEFAULT_PROJECT_URL,
           default_secret: process.env.NODE_RED_DEFAULT_PROJECT_SECRET
       }
   }
  1. add a CLI to do the same after building & starting

@hyamanieu
Copy link

hyamanieu commented Aug 17, 2022

In case it might be of use, this is my current solution:

  1. I use a bash script add_secret.sh to add project secrets after I have accessed for the first time the nodered micro service and added the existing project (which creates a fresh .config.projects.json while I am not able to write the secret)
  2. I use a json template .config.projects.template.json

.config.projects.template.json:

{
    "projects": {
        "$PROJECTNAME": {
            "credentialSecret": "$PROJECTSECRET"
        }
    },
    "activeProject": "$PROJECTNAME"
}

add_secret.sh (dont forget to chmod +x):

#!/bin/bash
# cd into this sh file directory
cd "$(dirname "$0")"

CONTAINERID=$1
PROJECTNAME=$2


! test -z "$CONTAINERID"; TESTCONTAINERID=$?
! test -z "$PROJECTNAME"; TESTPROJECTNAME=$?

#if both are missing, then loop every node-red container
if (test -z "$CONTAINERID" ) && (test -z "$PROJECTNAME" )
then
    for CONTAINERID in $(docker ps -q -f "label=com.docker.compose.project=node-red")
    do
        CONTAINERNAME=$(docker ps --filter Id=$CONTAINERID --format "{{.Names}}")
        PROJECTNAME=$CONTAINERNAME
        echo "Processing $PROJECTNAME secret..."
        ./add_secret.sh $CONTAINERID $PROJECTNAME
    done
#if one of them are missing, then prompt help
elif [ $TESTCONTAINERID -ne $TESTPROJECTNAME ]
then
    echo "Usage: $0 CONTAINERID PROJECTNAME"
    echo "  CONTAINERID should be the ID of the docker container"
    echo "    running the nodered application"
    echo "  PROJECTNAME should be the exact name of the project"
    echo "If both parameters are missing, it will run it for"
    echo "each container within the docker compose 'node-red' project."
#if both parameters are present, check the project secret is correctly copied.
else
    PROJECTSECRET=$(docker exec -it $CONTAINERID cat /data/.config.projects.json | jsonpointer /projects/$PROJECTNAME/credentialSecret /dev/stdin)
    if [ -z "$PROJECTSECRET" ]
    then
        echo "  Credential secret for project $PROJECTNAME is missing."
        echo "  Secret for project $PROJECTNAME (no prompt): "
        read -s PROJECTSECRET
        export PROJECTSECRET PROJECTNAME
        tmpfile=$(mktemp)
        cat ./data/.config.projects.template.json | envsubst > $tmpfile
        docker cp $tmpfile $CONTAINERID:/data/.config.projects.json
        docker restart $CONTAINERID
    else
        echo "  Secret already present"
    fi
fi

I would still prefer that upon starting the container for the first time, it detects the active project, or at least writing the secret upon clicking "open an existing project" over the dashboard as mentioned in my previous post.

edit:
after adding the line docker restart $CONTAINERID, it works by accessing the first time a newly built container.

@OptifySudarshanPatil
Copy link

@truongminh that is not the case for Projects.

The credentialSecret used by projects is provided by the user - not generated - and is stored in .config.projects.json.

@promd where you say:

When I open the credential settings, I can enter the credentials - but hitting save will only get me back to the same screen.

I have pushed a fix for that part - 85e05b7 - so the Open Project dialog will close and you can click the 'Setup credentials' link in the notification.

Where you say:

When I open the credential settings, I can enter the credentials - but hitting save will only get me back to the same screen.

Which 'same screen' are you referring to? I still haven't been able to recreate this path - for me, setting the correct credential key works.

copying "flows.json", ".config.projects.json" and "flows_cred.json" works for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants