One place for hosting & domains

      July 2020

      How to Install MongoDB on Ubuntu 20.04


      An earlier version of this tutorial was written by Melissa Anderson.

      Introduction

      MongoDB, also known as Mongo, is an open-source document database used in many modern web applications. It is classified as a NoSQL database because it does not rely on a traditional table-based relational database structure.

      Instead, it uses JSON-like documents with dynamic schemas, meaning that, unlike relational databases, MongoDB does not require a predefined schema before you add data to a database. You can alter the schema at any time and as often as is necessary without having to set up a new database with an updated schema.

      In this tutorial you’ll install MongoDB on an Ubuntu 20.04 server, test it, and learn how to manage it as a systemd service.

      Prerequisites

      To follow this tutorial, you will need:

      Step 1 — Installing MongoDB

      Ubuntu’s official package repositories include a stable version of MongoDB. However, as of this writing, the version of MongoDB available from the default Ubuntu repositories is 3.6, while the latest stable release is 4.4.

      To obtain the most recent version of this software, you must include MongoDB’s dedicated package repository to your APT sources. Then, you’ll be able to install mongodb-org, a meta-package that always points to the latest version of MongoDB.

      To start, import the public GPG key for the latest stable version of MongoDB. You can find the appropriate key file by navigating to the MongoDB key server and finding the file that includes the latest stable version number and ends in .asc. For example, if you want to install version 4.4 of MongoDB, you’d look for the file named server-4.4.asc.

      Right-click on the file, and select Copy link address. Then, paste that link into the following curl command, replacing the highlighted URL:

      • curl -fsSL https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -

      cURL is a command line tool available on many operating systems used to transfer data. It reads whatever data is stored at the URL passed to it and prints the content to the system’s output. In the following example, cURL prints the content of the GPG key file and then pipes it into the following sudo apt-key add - command, thereby adding the GPG key to your list of trusted keys.

      Also, note that this curl command uses the options -fsSL which, together, essentially tell cURL to fail silently. This means that if for some reason cURL isn’t able to contact the GPG server or the GPG server is down, it won’t accidentally add the resulting error code to your list of trusted keys.

      This command will return OK if the key was added successfully:

      Output

      OK

      If you’d like to double check that the key was added correctly, you can do so with the following command:

      This will return the MongoDB key somewhere in the output:

      Output

      /etc/apt/trusted.gpg -------------------- pub rsa4096 2019-05-28 [SC] [expires: 2024-05-26] 2069 1EEC 3521 6C63 CAF6 6CE1 6564 08E3 90CF B1F5 uid [ unknown] MongoDB 4.4 Release Signing Key <[email protected]> . . .

      At this point, your APT installation still doesn’t know where to find the mongodb-org package you need to install the latest version of MongoDB.

      There are two places on your server where APT looks for online sources of packages to download and install: the sources.list file and the sources.list.d directory. sources.list is a file that lists active sources of APT data, with one source per line and the most preferred sources listed first. The sources.list.d directory allows you to add such sources.list entries as separate files.

      Run the following command, which creates a file in the sources.list.d directory named mongodb-org-4.4.list. The only content in this file is a single line reading deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse:

      • echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list

      This single line tells APT everything it needs to know about what the source is and where to find it:

      • deb: This means that the source entry references a regular Debian architecture. In other cases, this part of the line might read deb-src, which means the source entry represents a Debian distribution’s source code.
      • [ arch=amd64,arm64 ]: This specifies which architectures the APT data should be downloaded to. In this case, it specifies the amd64 and arm64 architectures.
      • https://repo.mongodb.org/apt/ubuntu: This is a URI representing the location where the APT data can be found. In this case, the URI points to the HTTPS address where the official MongoDB repository is located.
      • focal/mongodb-org/4.4: Ubuntu repositories can contain several different releases. This specifies that you only want version 4.4 of the mongodb-org package available for the focal release of Ubuntu (“Focal Fossa” being the code name of Ubuntu 20.04).
      • multiverse: This part points APT to one of the four main Ubuntu repositories. In this case, it’s pointing to the multiverse repository.

      After running this command, update your server’s local package index so APT knows where to find the mongodb-org package:

      Following that, you can install MongoDB:

      • sudo apt install mongodb-org

      When prompted, press Y and then ENTER to confirm that you want to install the package.

      When the command finishes, MongoDB will be installed on your system. However it isn’t yet ready to use. Next, you’ll start MongoDB and confirm that it’s working correctly.

      Step 2 — Starting the MongoDB Service and Testing the Database

      The installation process described in the previous step automatically configures MongoDB to run as a daemon controlled by systemd, meaning you can manage MongoDB using the various systemctl commands. However, this installation procedure doesn’t automatically start the service.

      Run the following systemctl command to start the MongoDB service:

      • sudo systemctl start mongod.service

      Then check the service’s status. Notice that this command doesn’t include .service in the service file definition. systemctl will append this suffix to whatever argument you pass automatically if it isn’t already present, so it isn’t necessary to include it:

      • sudo systemctl status mongod

      This command will return output like the following, indicating that the service is up and running:

      Output

      ● mongod.service - MongoDB Database Server Loaded: loaded (/lib/systemd/system/mongod.service; disabled; vendor preset: enabled) Active: active (running) since Tue 2020-06-09 12:57:06 UTC; 2s ago Docs: https://docs.mongodb.org/manual Main PID: 37128 (mongod) Memory: 64.8M CGroup: /system.slice/mongod.service └─37128 /usr/bin/mongod --config /etc/mongod.conf

      After confirming that the service is running as expected, enable the MongoDB service to start up at boot:

      • sudo systemctl enable mongod

      You can further verify that the database is operational by connecting to the database server and executing a diagnostic command. The following command will connect to the database and output its current version, server address, and port. It will also return the result of MongoDB’s internal connectionStatus command:

      • mongo --eval 'db.runCommand({ connectionStatus: 1 })'

      connectionStatus will check and return the status of the database connection. A value of 1 for the ok field in the response indicates that the server is working as expected:

      Output

      MongoDB shell version v4.4.0 connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb Implicit session: session { "id" : UUID("1dc7d67a-0af5-4394-b9c4-8a6db3ff7e64") } MongoDB server version: 4.4.0 { "authInfo" : { "authenticatedUsers" : [ ], "authenticatedUserRoles" : [ ] }, "ok" : 1 }

      Also, note that the database is running on port 27017 on 127.0.0.1, the local loopback address representing localhost. This is MongoDB’s default port number.

      Next, we’ll look at how to manage the MongoDB server instance with systemd.

      Step 3 — Managing the MongoDB Service

      As mentioned previously, the installation process described in Step 1 configures MongoDB to run as a systemd service. This means that you can manage it using standard systemctl commands as you would with other Ubuntu system services.

      As mentioned previously, the systemctl status command checks the status of the MongoDB service:

      • sudo systemctl status mongod

      You can stop the service anytime by typing:

      • sudo systemctl stop mongod

      To start the service when it’s stopped, run:

      • sudo systemctl start mongod

      You can also restart the server when it’s already running:

      • sudo systemctl restart mongod

      In Step 2, you enabled MongoDB to start automatically with the server. If you ever wish to disable this automatic startup, type:

      • sudo systemctl disable mongod

      Then to re-enable it to start up at boot, run the enable command again:

      • sudo systemctl enable mongod

      For more information on how to manage systemd services, check out Systemd Essentials: Working with Services, Units, and the Journal.

      Conclusion

      In this tutorial, you added the official MongoDB repository to your APT instance, and installed the latest version of MongoDB. You then tested Mongo’s functionality and practiced some systemctl commands.

      As an immediate next step, we strongly recommend that you harden your MongoDB installation’s security by following our guide on How To Secure MongoDB on Ubuntu 20.04. Once it’s secured, you could then configure MongoDB to accept remote connections.

      You can find more tutorials on how to configure and use MongoDB in these DigitalOcean community articles. We also encourage you to check out the official MongoDB documentation, as it’s a great resource on the possibilities that MongoDB provides.



      Source link

      How To Launch Child Processes in Node.js


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      When a user executes a single Node.js program, it runs as a single operating system (OS) process that represents the instance of the program running. Within that process, Node.js executes programs on a single thread. As mentioned earlier in this series with the How To Write Asynchronous Code in Node.js tutorial, because only one thread can run on one process, operations that take a long time to execute in JavaScript can block the Node.js thread and delay the execution of other code. A key strategy to work around this problem is to launch a child process, or a process created by another process, when faced with long-running tasks. When a new process is launched, the operating system can employ multiprocessing techniques to ensure that the main Node.js process and the additional child process run concurrently, or at the same time.

      Node.js includes the child_process module, which has functions to create new processes. Aside from dealing with long-running tasks, this module can also interface with the OS and run shell commands. System administrators can use Node.js to run shell commands to structure and maintain their operations as a Node.js module instead of shell scripts.

      In this tutorial, you will create child processes while executing a series of sample Node.js applications. You’ll create processes with the child_process module by retrieving the results of a child process via a buffer or string with the exec() function, and then from a data stream with the spawn() function. You’ll finish by using fork() to create a child process of another Node.js program that you can communicate with as it’s running. To illustrate these concepts, you will write a program to list the contents of a directory, a program to find files, and a web server with multiple endpoints.

      Prerequisites

      Step 1 — Creating a Child Process with exec()

      Developers commonly create child processes to execute commands on their operating system when they need to manipulate the output of their Node.js programs with a shell, such as using shell piping or redirection. The exec() function in Node.js creates a new shell process and executes a command in that shell. The output of the command is kept in a buffer in memory, which you can accept via a callback function passed into exec().

      Let’s begin creating our first child processes in Node.js. First, we need to set up our coding environment to store the scripts we’ll create throughout this tutorial. In the terminal, create a folder called child-processes:

      Enter that folder in the terminal with the cd command:

      Create a new file called listFiles.js and open the file in a text editor. In this tutorial we will use nano, a terminal text editor:

      We’ll be writing a Node.js module that uses the exec() function to run the ls command. The ls command list the files and folders in a directory. This program takes the output from the ls command and displays it to the user.

      In the text editor, add the following code:

      ~/child-processes/listFiles.js

      const { exec } = require('child_process');
      
      exec('ls -lh', (error, stdout, stderr) => {
        if (error) {
          console.error(`error: ${error.message}`);
          return;
        }
      
        if (stderr) {
          console.error(`stderr: ${stderr}`);
          return;
        }
      
        console.log(`stdout:n${stdout}`);
      });
      

      We first import the exec() command from the child_process module using JavaScript destructuring. Once imported, we use the exec() function. The first argument is the command we would like to run. In this case, it’s ls -lh, which lists all the files and folders in the current directory in long format, with a total file size in human-readable units at the top of the output.

      The second argument is a callback function with three parameters: error, stdout, and stderr. If the command failed to run, error will capture the reason why it failed. This can happen if the shell cannot find the command you’re trying to execute. If the command is executed successfully, any data it writes to the standard output stream is captured in stdout, and any data it writes to the standard error stream is captured in stderr.

      Note: It’s important to keep the difference between error and stderr in mind. If the command itself fails to run, error will capture the error. If the command runs but returns output to the error stream, stderr will capture it. The most resilient Node.js programs will handle all possible outputs for a child process.

      In our callback function, we first check if we received an error. If we did, we display the error’s message (a property of the Error object) with console.error() and end the function with return. We then check if the command printed an error message and return if so. If the command successfully executes, we log its output to the console with console.log().

      Let’s run this file to see it in action. First, save and exit nano by pressing CTRL+X.

      Back in your terminal, run your application with the node command:

      Your terminal will display the following output:

      Output

      stdout: total 4.0K -rw-rw-r-- 1 sammy sammy 280 Jul 27 16:35 listFiles.js

      This lists the contents of the child-processes directory in long format, along with the size of the contents at the top. Your results will have your own user and group in place of sammy. This shows that the listFiles.js program successfully ran the shell command ls -lh.

      Now let’s look at another way to execute concurrent processes. Node.js’s child_process module can also run executable files with the execFile() function. The key difference between the execFile() and exec() functions is that the first argument of execFile() is now a path to an executable file instead of a command. The output of the executable file is stored in a buffer like exec(), which we access via a callback function with error, stdout, and stderr parameters.

      Note: Scripts in Windows such as .bat and .cmd files cannot be run with execFile() because the function does not create a shell when running the file. On Unix, Linux, and macOS, executable scripts do not always need a shell to run. However, a Windows machines needs a shell to execute scripts. To execute script files on Windows, use exec(), since it creates a new shell. Alternatively, you can use spawn(), which you’ll use later in this Step.

      However, note that you can execute .exe files in Windows successfully using execFile(). This limitation only applies to script files that require a shell to execute.

      Let’s begin by adding an executable script for execFile() to run. We’ll write a bash script that downloads the Node.js logo from the Node.js website and Base64 encodes it to convert its data to a string of ASCII characters.

      Create a new shell script file called processNodejsImage.sh:

      • nano processNodejsImage.sh

      Now write a script to download the image and base64 convert it:

      ~/child-processes/processNodejsImage.sh

      #!/bin/bash
      curl -s https://nodejs.org/static/images/logos/nodejs-new-pantone-black.svg > nodejs-logo.svg
      base64 nodejs-logo.svg
      

      The first statement is a shebang statement. It’s used in Unix, Linux, and macOS when we want to specify a shell to execute our script. The second statement is a curl command. The cURL utility, whose command is curl, is a command-line tool that can transfer data to and from a server. We use cURL to download the Node.js logo from the website, and then we use redirection to save the downloaded data to a new file nodejs-logo.svg. The last statement uses the base64 utility to encode the nodejs-logo.svg file we downloaded with cURL. The script then outputs the encoded string to the console.

      Save and exit before continuing.

      In order for our Node program to run the bash script, we have to make it executable. To do this, run the following:

      • chmod u+x processNodejsImage.sh

      This will give your current user the permission to execute the file.

      With our script in place, we can write a new Node.js module to execute it. This script will use execFile() to run the script in a child process, catching any errors and displaying all output to console.

      In your terminal, make a new JavaScript file called getNodejsImage.js:

      Type the following code in the text editor:

      ~/child-processes/getNodejsImage.js

      const { execFile } = require('child_process');
      
      execFile(__dirname + '/processNodejsImage.sh', (error, stdout, stderr) => {
        if (error) {
          console.error(`error: ${error.message}`);
          return;
        }
      
        if (stderr) {
          console.error(`stderr: ${stderr}`);
          return;
        }
      
        console.log(`stdout:n${stdout}`);
      });
      

      We use JavaScript destructuring to import the execFile() function from the child_process module. We then use that function, passing the file path as the first name. __dirname contains the directory path of the module in which it is written. Node.js provides the __dirname variable to a module when the module runs. By using __dirname, our script will always find the processNodejsImage.sh file across different operating systems, no matter where we run getNodejsImage.js. Note that for our current project setup, getNodejsImage.js and processNodejsImage.sh must be in the same folder.

      The second argument is a callback with the error, stdout, and stderr parameters. Like with our previous example that used exec(), we check for each possible output of the script file and log them to the console.

      In your text editor, save this file and exit from the editor.

      In your terminal, use node to execute the module:

      Running this script will produce output like this:

      Output

      stdout: PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNDQyLjQgMjcwLjkiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjE4MC43IiB5MT0iODAuNyIge ...

      Note that we truncated the output in this article because of its large size.

      Before base64 encoding the image, processNodejsImage.sh first downloads it. You can also verify that you downloaded the image by inspecting the current directory.

      Execute listFiles.js to find the updated list of files in our directory:

      The script will display content similar to the following on the terminal:

      Output

      stdout: total 20K -rw-rw-r-- 1 sammy sammy 316 Jul 27 17:56 getNodejsImage.js -rw-rw-r-- 1 sammy sammy 280 Jul 27 16:35 listFiles.js -rw-rw-r-- 1 sammy sammy 5.4K Jul 27 18:01 nodejs-logo.svg -rwxrw-r-- 1 sammy sammy 129 Jul 27 17:56 processNodejsImage.sh

      We’ve now successfully executed processNodejsImage.sh as a child process in Node.js using the execFile() function.

      The exec() and execFile() functions can run commands on the operating system’s shell in a Node.js child process. Node.js also provides another method with similar functionality, spawn(). The difference is that instead of getting the output of the shell commands all at once, we get them in chunks via a stream. In the next section we’ll use the spawn() command to create a child process.

      Step 2 — Creating a Child Process with spawn()

      The spawn() function runs a command in a process. This function returns data via the stream API. Therefore, to get the output of the child process, we need to listen for stream events.

      Streams in Node.js are instances of event emitters. If you would like to learn more about listening for events and the foundations of interacting with streams, you can read our guide on Using Event Emitters in Node.js.

      It’s often a good idea to choose spawn() over exec() or execFile() when the command you want to run can output a large amount of data. With a buffer, as used by exec() and execFile(), all the processed data is stored in the computer’s memory. For large amounts of data, this can degrade system performance. With a stream, the data is processed and transferred in small chunks. Therefore, you can process a large amount of data without using too much memory at any one time.

      Let’s see how we can use spawn() to make a child process. We will write a new Node.js module that creates a child process to run the find command. We will use the find command to list all the files in the current directory.

      Create a new file called findFiles.js:

      In your text editor, begin by calling the spawn() command:

      ~/child-processes/findFiles.js

      const { spawn } = require('child_process');
      
      const child = spawn('find', ['.']);
      

      We first imported the spawn() function from the child_process module. We then called the spawn() function to create a child process that executes the find command. We hold the reference to the process in the child variable, which we will use to listen to its streamed events.

      The first argument in spawn() is the command to run, in this case find. The second argument is an array that contains the arguments for the executed command. In this case, we are telling Node.js to execute the find command with the argument ., thereby making the command find all the files in the current directory. The equivalent command in the terminal is find ..

      With the exec() and execFile() functions, we wrote the arguments along with the command in one string. However, with spawn(), all arguments to commands must be entered in the array. That’s because spawn(), unlike exec() and execFile(), does not create a new shell before running a process. To have commands with their arguments in one string, you need Node.js to create a new shell as well.

      Let’s continue our module by adding listeners for the command’s output. Add the following highlighted lines:

      ~/child-processes/findFiles.js

      const { spawn } = require('child_process');
      
      const child = spawn('find', ['.']);
      
      child.stdout.on('data', data => {
        console.log(`stdout:n${data}`);
      });
      
      child.stderr.on('data', data => {
        console.error(`stderr: ${data}`);
      });
      

      Commands can return data in either the stdout stream or the stderr stream, so you added listeners for both. You can add listeners by calling the on() method of each streams’ objects. The data event from the streams gives us the command’s output to that stream. Whenever we get data on either stream, we log it to the console.

      We then listen to two other events: the error event if the command fails to execute or is interrupted, and the close event for when the command has finished execution, thus closing the stream.

      In the text editor, complete the Node.js module by writing the following highlighted lines:

      ~/child-processes/findFiles.js

      const { spawn } = require('child_process');
      
      const child = spawn('find', ['.']);
      
      child.stdout.on('data', (data) => {
        console.log(`stdout:n${data}`);
      });
      
      child.stderr.on('data', (data) => {
        console.error(`stderr: ${data}`);
      });
      
      child.on('error', (error) => {
        console.error(`error: ${error.message}`);
      });
      
      child.on('close', (code) => {
        console.log(`child process exited with code ${code}`);
      });
      

      For the error and close events, you set up a listener directly on the child variable. When listening for error events, if one occurs Node.js provides an Error object. In this case, you log the error’s message property.

      When listening to the close event, Node.js provides the exit code of the command. An exit code denotes if the command ran successfully or not. When a command runs without errors, it returns the lowest possible value for an exit code: 0. When executed with an error, it returns a non-zero code.

      The module is complete. Save and exit nano with CTRL+X.

      Now, run the code with the node command:

      Once complete, you will find the following output:

      Output

      stdout: . ./findFiles.js ./listFiles.js ./nodejs-logo.svg ./processNodejsImage.sh ./getNodejsImage.js child process exited with code 0

      We find a list of all files in our current directory and the exit code of the command, which is 0 as it ran successfully. While our current directory has a small number of files, if we ran this code in our home directory, our program would list every single file in every accessible folder for our user. Because it has such a potentially large output, using the spawn() function is most ideal as its streams do not require as much memory as a large buffer.

      So far we’ve used functions to create child processes to execute external commands in our operating system. Node.js also provides a way to create a child process that executes other Node.js programs. Let’s use the fork() function to create a child process for a Node.js module in the next section.

      Step 3 — Creating a Child Process with fork()

      Node.js provides the fork() function, a variation of spawn(), to create a child process that’s also a Node.js process. The main benefit of using fork() to create a Node.js process over spawn() or exec() is that fork() enables communication between the parent and the child process.

      With fork(), in addition to retrieving data from the child process, a parent process can send messages to the running child process. Likewise, the child process can send messages to the parent process.

      Let’s see an example where using fork() to create a new Node.js child process can improve the performance of our application. Node.js programs run on a single process. Therefore, CPU intensive tasks like iterating over large loops or parsing large JSON files stop other JavaScript code from running. For certain applications, this is not a viable option. If a web server is blocked, then it cannot process any new incoming requests until the blocking code has completed its execution.

      Let’s see this in practice by creating a web server with two endpoints. One endpoint will do a slow computation that blocks the Node.js process. The other endpoint will return a JSON object saying hello.

      First, create a new file called httpServer.js, which will have the code for our HTTP server:

      We’ll begin by setting up the HTTP server. This involves importing the http module, creating a request listener function, creating a server object, and listening for requests on the server object. If you would like to dive deeper into creating HTTP servers in Node.js or would like a refresher, you can read our guide on How To Create a Web Server in Node.js with the HTTP Module.

      Enter the following code in your text editor to set up an HTTP server:

      ~/child-processes/httpServer.js

      const http = require('http');
      
      const host="localhost";
      const port = 8000;
      
      const requestListener = function (req, res) {};
      
      const server = http.createServer(requestListener);
      server.listen(port, host, () => {
        console.log(`Server is running on http://${host}:${port}`);
      });
      

      This code sets up an HTTP server that will run at http://localhost:8000. It uses template literals to dynamically generate that URL.

      Next, we will write an intentionally slow function that counts in a loop 5 billion times. Before the requestListener() function, add the following code:

      ~/child-processes/httpServer.js

      ...
      const port = 8000;
      
      const slowFunction = () => {
        let counter = 0;
        while (counter < 5000000000) {
          counter++;
        }
      
        return counter;
      }
      
      const requestListener = function (req, res) {};
      ...
      

      This uses the arrow function syntax to create a while loop that counts to 5000000000.

      To complete this module, we need to add code to the requestListener() function. Our function will call the slowFunction() on subpath, and return a small JSON message for the other. Add the following code to the module:

      ~/child-processes/httpServer.js

      ...
      const requestListener = function (req, res) {
        if (req.url === '/total') {
          let slowResult = slowFunction();
          let message = `{"totalCount":${slowResult}}`;
      
          console.log('Returning /total results');
          res.setHeader('Content-Type', 'application/json');
          res.writeHead(200);
          res.end(message);
        } else if (req.url === '/hello') {
          console.log('Returning /hello results');
          res.setHeader('Content-Type', 'application/json');
          res.writeHead(200);
          res.end(`{"message":"hello"}`);
        }
      };
      ...
      

      If the user reaches the server at the /total subpath, then we run slowFunction(). If we are hit at the /hello subpath, we return this JSON message: {"message":"hello"}.

      Save and exit the file by pressing CTRL+X.

      To test, run this server module with node:

      When our server starts, the console will display the following:

      Output

      Server is running on http://localhost:8000

      Now, to test the performance of our module, open two additional terminals. In the first terminal, use the curl command to make a request to the /total endpoint, which we expect to be slow:

      • curl http://localhost:8000/total

      In the other terminal, use curl to make a request to the /hello endpoint like this:

      • curl http://localhost:8000/hello

      The first request will return the following JSON:

      Output

      {"totalCount":5000000000}

      Whereas the second request will return this JSON:

      Output

      {"message":"hello"}

      The request to /hello completed only after the request to /total. The slowFunction() blocked all other code from executing while it was still in its loop. You can verify this by looking at the Node.js server output that was logged in your original terminal:

      Output

      Returning /total results Returning /hello results

      To process the blocking code while still accepting incoming requests, we can move the blocking code to a child process with fork(). We will move the blocking code into its own module. The Node.js server will then create a child process when someone accesses the /total endpoint and listen for results from this child process.

      Refactor the server by first creating a new module called getCount.js that will contain slowFunction():

      Now enter the code for slowFunction() once again:

      ~/child-processes/getCount.js

      const slowFunction = () => {
        let counter = 0;
        while (counter < 5000000000) {
          counter++;
        }
      
        return counter;
      }
      

      Since this module will be a child process created with fork(), we can also add code to communicate with the parent process when slowFunction() has completed processing. Add the following block of code that sends a message to the parent process with the JSON to return to the user:

      ~/child-processes/getCount.js

      const slowFunction = () => {
        let counter = 0;
        while (counter < 5000000000) {
          counter++;
        }
      
        return counter;
      }
      
      process.on('message', (message) => {
        if (message == 'START') {
          console.log('Child process received START message');
          let slowResult = slowFunction();
          let message = `{"totalCount":${slowResult}}`;
          process.send(message);
        }
      });
      

      Let’s break down this block of code. The messages between a parent and child process created by fork() are accessible via the Node.js global process object. We add a listener to the process variable to look for message events. Once we receive a message event, we check if it’s the START event. Our server code will send the START event when someone accesses the /total endpoint. Upon receiving that event, we run slowFunction() and create a JSON string with the result of the function. We use process.send() to send a message to the parent process.

      Save and exit getCount.js by entering CTRL+X in nano.

      Now, let’s modify the httpServer.js file so that instead of calling slowFunction(), it creates a child process that executes getCount.js.

      Re-open httpServer.js with nano:

      First, import the fork() function from the child_process module:

      ~/child-processes/httpServer.js

      const http = require('http');
      const { fork } = require('child_process');
      ...
      

      Next, we are going to remove the slowFunction() from this module and modify the requestListener() function to create a child process. Change the code in your file so it looks like this:

      ~/child-processes/httpServer.js

      ...
      const port = 8000;
      
      const requestListener = function (req, res) {
        if (req.url === '/total') {
          const child = fork(__dirname + '/getCount');
      
          child.on('message', (message) => {
            console.log('Returning /total results');
            res.setHeader('Content-Type', 'application/json');
            res.writeHead(200);
            res.end(message);
          });
      
          child.send('START');
        } else if (req.url === '/hello') {
          console.log('Returning /hello results');
          res.setHeader('Content-Type', 'application/json');
          res.writeHead(200);
          res.end(`{"message":"hello"}`);
        }
      };
      ...
      

      When someone goes to the /total endpoint, we now create a new child process with fork(). The argument of fork() is the path to the Node.js module. In this case, it is the getCount.js file in our current directory, which we receive from __dirname. The reference to this child process is stored in a variable child.

      We then add a listener to the child object. This listener captures any messages that the child process gives us. In this case, getCount.js will return a JSON string with the total number counted by the while loop. When we receive that message, we send the JSON to the user.

      We use the send() function of the child variable to give it a message. This program sends the message START, which begins the execution of slowFunction() in the child process.

      Save and exit nano by entering CTRL+X.

      To test the improvement using fork() made on HTTP server, begin by executing the httpServer.js file with node:

      Like before, it will output the following message when it launches:

      Output

      Server is running on http://localhost:8000

      To test the server, we will need an additional two terminals as we did the first time. You can re-use them if they are still open.

      In the first terminal, use the curl command to make a request to the /total endpoint, which takes a while to compute:

      • curl http://localhost:8000/total

      In the other terminal, use curl to make a request to the /hello endpoint, which responds in a short time:

      • curl http://localhost:8000/hello

      The first request will return the following JSON:

      Output

      {"totalCount":5000000000}

      Whereas the second request will return this JSON:

      Output

      {"message":"hello"}

      Unlike the first time we tried this, the second request to /hello runs immediately. You can confirm by reviewing the logs, which will look like this:

      Output

      Child process received START message Returning /hello results Returning /total results

      These logs show that the request for the /hello endpoint ran after the child process was created but before the child process had finished its task.

      Since we moved the blocking code in a child process using fork(), the server was still able to respond to other requests and execute other JavaScript code. Because of the fork() function’s message passing ability, we can control when a child process begins an activity and we can return data from a child process to a parent process.

      Conclusion

      In this article, you used various functions to create a child process in Node.js. You first created child processes with exec() to run shell commands from Node.js code. You then ran an executable file with the execFile() function. You looked at the spawn() function, which can also run commands but returns data via a stream and does not start a shell like exec() and execFile(). Finally, you used the fork() function to allow for two-way communication between the parent and child process.

      To learn more about the child_process module, you can read the Node.js documentation. If you’d like to continue learning Node.js, you can return to the How To Code in Node.js series, or browse programming projects and setups on our Node topic page.



      Source link

      How To Secure MongoDB on Ubuntu 20.04


      An earlier version of this tutorial was written by Melissa Anderson.

      Introduction

      MongoDB, also known as Mongo, is an open-source document database used in many modern web applications. It is classified as a NoSQL database because it does not rely on a traditional table-based relational database structure. Instead, it uses JSON-like documents with dynamic schemas.

      MongoDB doesn’t have authentication enabled by default, meaning that any user with access to the server where the database is installed can add and delete data without restriction. In order to secure this vulnerability, this tutorial will walk you through creating an administrative user and enabling authentication. You’ll then test to confirm that only this administrative user has access to the database.

      Prerequisites

      To complete this tutorial, you will need the following:

      • A server running Ubuntu 20.04. This server should have a non-root administrative user and a firewall configured with UFW. Set this up by following our initial server setup guide for Ubuntu 20.04.
      • MongoDB installed on your server. This tutorial was validated using MongoDB version 4.4, though it should generally work for older versions of MongoDB as well. To install Mongo on your server, follow our tutorial on How To Install MongoDB on Ubuntu 20.04.

      Step 1 — Adding an Administrative User

      Since the release of version 3.0, the MongoDB daemon is configured to only accept connections from the local Unix socket, and it is not automatically open to the wider Internet. However, authentication is still disabled by default. This means that any users that have access to the server where MongoDB is installed also have complete access to the databases.

      As a first step to securing this vulnerability, you will create an administrative user. Later, you’ll enable authentication and connect as this administrative user to access the database.

      To add an administrative user, you must first connect to the Mongo shell. Because authentication is disabled you can do so with the mongo command, without any other options:

      There will be some output above the Mongo shell prompt. Because you haven’t yet enabled authentication, this will include a warning that access control isn’t enabled for the database and that read and write access to data and and the database’s configuration are unrestricted:

      Output

      MongoDB shell version v4.4.0 . . . 2020-06-09T13:26:51.391+0000 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database. 2020-06-09T13:26:51.391+0000 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted. . . . >

      These warnings will disappear after you enable authentication, but for now they mean anyone who can access your Ubuntu server could also take control over your database.

      To illustrate, run Mongo’s show dbs command:

      This command returns a list of every database on the server. However, when authentication is enabled, the list changes based on the Mongo user’s role, or what level of access it has to certain databases. Because authentication is disabled, though, it will return every database currently on the system without restrictions:

      Output

      admin 0.000GB config 0.000GB local 0.000GB

      In this example output, only the default databases appear. However, if you have any databases holding sensitive data on your system, any user could find them with this command.

      As part of mitigating this vulnerability, this step is focused on adding an administrative user. To do this, you must first connect to the admin database. This is where information about users, like their usernames, passwords, and roles, are stored:

      Output

      switched to db admin

      MongoDB comes installed with a number of JavaScript-based shell methods you can use to manage your database. One of these, the db.createUser method, is used to create new users on the database on which the method is run.

      Initiate the db.createUser method:

      This method requires you to specify a username and password for the user, as well as any roles you want the user to have. Recall that MongoDB stores its data in JSON-like documents. As such, when you create a new user, all you’re doing is creating a document to hold the appropriate user data as individual fields.

      As with objects in JSON, documents in MongoDB begin and end with curly braces ({ and }). To begin adding a user, enter an opening curly brace:

      Note: Mongo won’t register the db.createUser method as complete until you enter a closing parenthesis. Until you do, the prompt will change from a greater than sign (>) to an ellipsis (...).

      Next, enter a user: field, with your desired username as the value in double quotes followed by a comma. The following example specifies the username AdminSammy, but you can enter whatever username you like:

      Next, enter a pwd field with the passwordPrompt() method as its value. When you execute the db.createUser method, the passwordPrompt() method will provide a prompt for you to enter your password. This is more secure than the alternative, which is to type out your password in cleartext as you did for your username.

      Note: The passwordPrompt() method is only compatible with MongoDB versions 4.2 and newer. If you’re using an older version of Mongo, then you will have to write out your password in cleartext, similarly to how you wrote out your username:

      Be sure to follow this field with a comma as well:

      Then enter the roles you want your administrative user to have. Because you’re creating an administrative user, at a minimum you should grant them the userAdminAnyDatabase role over the admin database. This will allow the administrative user to create and modify new users and roles. Because the administrative user has this role in the admin database, this will also grant it superuser access to the entire cluster.

      In addition, the following example also grants the administrative user the readWriteAnyDatabase role. This grants the administrative user the ability to read and modify data on any database in the cluster except for the config and local databases, which are mostly for internal use:

      • roles: [ { role: "userAdminAnyDatabase", db: "admin" }, "readWriteAnyDatabase" ]

      Following that, enter a closing brace to signify the end of the document:

      Then enter a closing parenthesis to close and execute the db.createUser method:

      All together, here’s what your db.createUser method should look like:

      > db.createUser(
      ... {
      ... user: "AdminSammy",
      ... pwd: passwordPrompt(),
      ... roles: [ { role: "userAdminAnyDatabase", db: "admin" }, "readWriteAnyDatabase" ]
      ... }
      ... )
      

      If each line’s syntax is correct, the method will execute properly and you’ll be prompted to enter a password:

      Output

      Enter password:

      Enter a strong password of your choosing. Then, you’ll receive a confirmation that the user was added:

      Output

      Successfully added user: { "user" : "AdminSammy", "roles" : [ { "role" : "userAdminAnyDatabase", "db" : "admin" }, "readWriteAnyDatabase" ] }

      Following that, you can exit the MongoDB client:

      At this point, your user will be allowed to enter credentials. However, they will not be required to do so until you enable authentication and restart the MongoDB daemon.

      Step 2 — Enabling Authentication

      To enable authentication, you must edit mongod.conf, MongoDB’s configuration file. Once you enable it and restart the Mongo service, users will still be able to connect to the database without authenticating. However, they won’t be able to read or modify any data until they provide a correct username and password.

      Open the configuration file with your preferred text editor. Here, we’ll use nano:

      • sudo nano /etc/mongod.conf

      Scroll down to find the commented-out security section:

      /etc/mongod.conf

      . . .
      #security:
      
      #operationProfiling:
      
      . . .
      

      Uncomment this line by removing the pound sign (#):

      /etc/mongod.conf

      . . .
      security:
      
      #operationProfiling:
      
      . . .
      

      Then add the authorization parameter and set it to "enabled". When you’re done, the lines should look like this:

      /etc/mongod.conf

      . . .
      security:
        authorization: "enabled"
      . . . 
      

      Note that the security: line has no spaces at the beginning, while the authorization: line is indented with two spaces.

      After adding these lines, save and close the file. If you used nano to open the file, do so by pressing CTRL + X, Y, then ENTER.

      Then restart the daemon to put these new changes into effect:

      • sudo systemctl restart mongod

      Next, check the service’s status to make sure that it restarted correctly:

      • sudo systemctl status mongod

      If the restart command was successful, you’ll receive output that indicates that the mongod service is active and was recently started:

      Output

      ● mongod.service - MongoDB Database Server Loaded: loaded (/lib/systemd/system/mongod.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2020-06-09 22:06:20 UTC; 7s ago Docs: https://docs.mongodb.org/manual Main PID: 15370 (mongod) Memory: 170.1M CGroup: /system.slice/mongod.service └─15370 /usr/bin/mongod --config /etc/mongod.conf Jun 09 22:06:20 your_host systemd[1]: Started MongoDB Database Server.

      Having verified the daemon is back up and running, you can test that the authentication setting you added works as expected.

      Step 3 — Testing Authentication Settings

      To begin testing that the authentication requirements you added in the previous step are working correctly, start by connecting without specifying any credentials to verify that your actions are indeed restricted:

      Now that you’ve enabled authentication, none of the warnings you encountered previously will appear:

      Output

      MongoDB shell version v4.4.0 connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb Implicit session: session { "id" : UUID("5d50ed96-f7e1-493a-b4da-076067b2d898") } MongoDB server version: 4.4.0 >

      Confirm whether your access is restricted by running the show dbs command again:

      Recall from Step 1 that there are at least a few default databases on your server. However, in this case the command won’t have any output because you haven’t authenticated as a privileged user.

      Because this command doesn’t return any information, it’s safe to say the authentication setting is working as expected. You also won’t be able to create users or perform other privileged tasks without first authenticating.

      Go ahead and exit the MongoDB shell:

      Note: Instead of running the following exit command as you did previously in Step 1, an alternative way to close the shell is to just press CTRL + C.

      Next, make sure that your administrative user is able to authenticate properly by running the following mongo command to connect as this user. This command includes the -u flag, which precedes the name of the user you want to connect as. Be sure to replace AdminSammy with your own administrative user’s username. It also includes the -p flag, which will prompt you for the user’s password, and specifies admin as the authentication database where the specified username was created:

      • mongo -u AdminSammy -p --authenticationDatabase admin

      Enter the user’s password when prompted, and then you’ll be dropped into the shell. Once there, try issuing the show dbs command again:

      This time, because you’ve authenticated properly, the command will successfully return a list of all the databases currently on the server:

      Output

      admin 0.000GB config 0.000GB local 0.000GB

      This confirms that authentication was enabled successfully.

      Conclusion

      By completing this guide, you’ve set up an administrative MongoDB user which you can employ to create and modify new users and roles, and otherwise manage your MongoDB instance. You also configured your MongoDB instance to require that users authenticate with a valid username and password before they can interact with any data.

      For more information on how to manage MongoDB users, check out the official documentation on the subject. You may also be interested in learning more about how authentication works on MongoDB.

      Also, if you plan to interact with your MongoDB instance remotely, you can follow our guide on How To Configure Remote Access for MongoDB on Ubuntu 20.04.



      Source link