One place for hosting & domains

      September 2021

      How To Install Ruby on Rails with rbenv on Ubuntu 20.04


      Introduction

      Ruby on Rails is one of the most popular application stacks for developers looking to create sites and web apps. The Ruby programming language, combined with the Rails development framework, allows you to build and deploy scalable apps quickly.

      You can install Ruby and Rails with the command line tool rbenv. Using rbenv provides you with a solid environment for developing your Ruby on Rails applications and allows you to switch between Ruby versions, keeping your entire team on the same version. rbenv also provides support for specifying application-specific versions of Ruby, allows you to change the global Ruby for each user, and the option to use an environment variable to override the Ruby version.

      In this tutorial, we will guide you through the Ruby and Rails installation processes with rbenv and gem. First, you’ll install the appropriate packages to install rbenv and then Ruby. After, you’ll install the ruby-build plugin so that you can install available versions of Ruby. Last, you’ll use gem to install Rails and can then use Ruby on Rails to begin your web development. We will also provide steps on how to check if your rbenv version is up-to-date, and how to uninstall Ruby versions and rbenv.

      Prerequisites

      To follow this tutorial, you need:

      Step 1 – Install rbenv and Dependencies

      Ruby relies on several packages that you can install through your package manager. Once those are installed, you can install rbenv and use it to install Ruby.

      First, update your package list:

      Next, install the dependencies required to install Ruby:

      • sudo apt install git curl libssl-dev libreadline-dev zlib1g-dev autoconf bison build-essential libyaml-dev libreadline-dev libncurses5-dev libffi-dev libgdbm-dev

      After installing the dependencies, you can install rbenv itself. Use curl to transfer information from the rbenv repository on GitHub into the directory ~/.rbenv:

      • curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash

      Next, add ~/.rbenv/bin to your $PATH so that you can use the rbenv command line utility. Do this by altering your ~/.bashrc file so that it affects future login sessions:

      • echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc

      Then, add the command eval "$(rbenv init -)" to your ~/.bashrc file so rbenv loads automatically:

      • echo 'eval "$(rbenv init -)"' >> ~/.bashrc

      Next, apply the changes you made to your ~/.bashrc file to your current shell session:

      Verify that rbenv is set up properly by running the type command, which will display more information about the rbenv command:

      Your terminal window will display the following:

      Output

      rbenv is a function rbenv () { local command; command="${1:-}"; if [ "$#" -gt 0 ]; then shift; fi; case "$command" in rehash | shell) eval "$(rbenv "sh-$command" "$@")" ;; *) command rbenv "$command" "$@" ;; esac }

      Next, install the ruby-build plugin. This plugin adds the rbenv install command, which makes the installation process of new versions of Ruby less complex. To install ruby-build, first clone the ruby-build GitHub repository:

      • git clone https://github.com/rbenv/ruby-build.git

      After running this command, you’ll have a directory named ruby-build in your working directory. Within the ruby-build directory is a script named install.sh which you’ll use to actually install ruby-build.

      Before running this script, take a moment to review its contents. Rather than opening the file with a text editor, you can print its contents to your terminal’s output with the following command:

      • cat ruby-build/install.sh

      Output

      #!/bin/sh # Usage: PREFIX=/usr/local ./install.sh # # Installs ruby-build under $PREFIX. set -e cd "$(dirname "$0")" if [ -z "${PREFIX}" ]; then PREFIX="/usr/local" fi BIN_PATH="${PREFIX}/bin" SHARE_PATH="${PREFIX}/share/ruby-build" mkdir -p "$BIN_PATH" "$SHARE_PATH" install -p bin/* "$BIN_PATH" install -p -m 0644 share/ruby-build/* "$SHARE_PATH"

      Notice the second line of this file that reads # Usage: PREFIX=/usr/local ./install.sh. This commented-out line explains that in order to execute this script and install ruby-build, you must precede the script with PREFIX=/usr/local. This will create a temporary environment variable that will affect how the script is run. Essentially, this will cause the string $PREFIX to be replaced with /usr/local any time it appears in the script and will ultimately cause all the necessary ruby-build files to be installed within the /usr/local directory. This environment variable is only temporary and will cease to exist once the script terminates.

      Create this temporary environment variable and run the script with the following command. Note that this command includes sudo before calling the script. This is necessary since you must have advanced privileges to install files to the /usr/local directory:

      • PREFIX=/usr/local sudo ./ruby-build/install.sh

      At this point, you have both rbenv and ruby-build installed. Let’s install Ruby next.

      Step 2 – Installing Ruby with ruby-build

      With the ruby-build plugin now installed, you can install whatever versions of Ruby that you may need with a single command. First, list all the available versions of Ruby:

      The output of that command will be a list of versions that you can choose to install:

      Output

      2.6.8 2.7.4 3.0.2 jruby-9.2.19.0 mruby-3.0.0 rbx-5.0 truffleruby-21.2.0.1 truffleruby+graalvm-21.2.0 Only latest stable releases for each Ruby implementation are shown. Use 'rbenv install --list-all / -L' to show all local versions.

      Now let’s install Ruby 3.0.2:

      Installing Ruby can be a lengthy process, so be prepared for the installation to take some time to complete.

      Once it’s done installing, set it as your default version of Ruby with the global sub-command:

      Verify that Ruby was properly installed by checking its version number:

      If you installed version 3.0.2 of Ruby, this command will return output like this:

      Output

      ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux]

      To install and use a different version of Ruby, run the rbenv commands with a different version number, as in rbenv install 2.3.0 followed by rbenv global 2.3.0.

      You now have at least one version of Ruby installed and have set your default Ruby version. Next, you will set up gems and Rails.

      Step 3 – Working with Gems

      Gems are the way Ruby libraries are distributed. You use the gem command to manage these gems, and use this command to install Rails.

      When you install a gem, the installation process generates local documentation. This can add a significant amount of time to each gem’s installation process, so turn off local documentation generation by creating a file called ~/.gemrc which contains a configuration setting to turn off this feature:

      • echo "gem: --no-document" > ~/.gemrc

      Bundler is a tool that manages gem dependencies for projects. Install the Bundler gem next, as Rails depends on it:

      You’ll receive the following output:

      Output

      Fetching bundler-2.2.27.gem Successfully installed bundler-2.2.27 1 gem installed

      You can use the gem env command (the subcommand env is short for environment) to learn more about the environment and configuration of gems. You can confirm where gems are being installed by using the home argument, like this:

      You’ll receive an output similar to this:

      Output

      /home/sammy/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0

      Once you have gems set up, you can install Rails.

      Step 4 – Installing Rails

      To install Rails, use the gem install command along with the -v flag to specify the version. For this tutorial, you’ll use version 6.1.4.1:

      • gem install rails -v 6.1.4.1

      The gem command installs the gem you specify, as well as any of its dependencies. Rails is a complex web development framework and has many dependencies, so the process will take some time to complete. Eventually, you’ll receive a message stating that Rails is installed, along with its dependencies:

      Output

      ... Successfully installed rails-6.1.4.1 37 gems installed

      Note: If you would like to install a different version of Rails, you can list the valid versions of Rails by doing a search, which will output a list of possible versions. You can then install a specific version, such as 4.2.7:

      • gem search '^rails$' --all
      • gem install rails -v 4.2.7

      If you would like to install the latest version of Rails, run the command without a version specified:

      rbenv works by creating a directory of shims, which point to the files used by the Ruby version that’s currently enabled. Through the rehash sub-command, rbenv maintains shims in that directory to match every Ruby command across every installed version of Ruby on your server. Whenever you install a new version of Ruby or a gem that provides commands as Rails does, you should run the following:

      Verify that Rails has been installed properly by printing its version, with the following command:

      If it’s installed properly, this command will return the version of Rails that was installed:

      Output

      Rails 6.1.4.1

      At this point, you can begin testing your Ruby on Rails installation and start to develop web applications. Now let’s review how to keep the rbenv up-to-date.

      Step 5 – Updating rbenv

      Since you installed rbenv manually using Git, you can upgrade your installation to the most recent version at any time by using the git pull command in the ~/.rbenv directory:

      This will ensure that you are using the most up-to-date version of rbenv available.

      Step 6 – Uninstalling Ruby versions

      As you download additional versions of Ruby, you may accumulate more versions than you would like in your ~/.rbenv/versions directory. Use the ruby-build plugin’s uninstall subcommand to remove these previous versions.

      The following command will uninstall Ruby version 3.0.2:

      With the rbenv uninstall command you can clean up old versions of Ruby so that you do not have more installed than you are currently using.

      Step 7 – Uninstalling rbenv

      If you’ve decided you no longer want to use rbenv, you can remove it from your system.

      To do this, first open your ~/.bashrc file in your editor. In this example, we will use nano:

      Find and remove the following two lines from the file:

      ~/.bashrc

      ...
      export PATH="$HOME/.rbenv/bin:$PATH"
      eval "$(rbenv init -)"
      

      After removing these lines, save the file and exit the editor. If you used nano, you can exit by pressing CTRL + X then Y and ENTER.

      Then remove rbenv and all installed Ruby versions with the following command:

      Log out and back in to apply the changes to your shell.

      Conclusion

      In this tutorial, you installed rbenv and gem to install the entire Ruby on Rails framework. From here, you can begin creating your web development application projects. If you want to learn more about making those environments more robust you can check out our series on How To Code In Ruby.



      Source link

      How To Build a Python REST API with Fauna and Deploy it to DigitalOcean App Platform


      Introduction

      Many developers don’t have the time or experience to set up and manage infrastructure for their applications. To keep up with deadlines and reduce costs, developers need to find solutions that allow them to deploy their apps to the cloud as quickly and efficiently as possible to focus on writing the code and delivering new features to their customers. Together, DigitalOcean’s App Platform and Fauna provide that ability.

      DigitalOcean App Platform is a Platform-as-a-Service (PaaS) that abstracts the infrastructure that runs your apps. It also lets you deploy applications by pushing your code to a Git branch.

      Fauna is a powerful data layer for applications of any size. As you’ll see in this tutorial, with Fauna, you can get a database up and running quickly without having to worry about the database operations.

      Together, these two solutions let you focus on your application instead of managing your infrastructure.

      In this tutorial, you’ll integrate Fauna with Python by writing a minimal REST API using the Flask framework. You’ll then deploy the API to DigitalOcean’s App Platform from a Git repository.The API will consist of:

      • A public /signup POST endpoint for creating users in the Users collection.
      • A public /login POST endpoint for authenticating with the documents in the Users collection.
      • A private /things GET endpoint for fetching a list of Fauna documents from the Things collection.

      The finished Python project is available at this Github repository.

      Prerequisites

      Before starting this tutorial, you will need:

      Step 1 — Setting Up the Fauna Database

      In the first step, you will configure a Fauna database and create the collections for the API. Fauna is a document-based database rather than a traditional table-based relational database. Fauna stores your data in documents and collections, which are groups of documents.

      To create the collections, you will execute queries using FQL, Fauna’s native query language. FQL is an expressive and powerful query language that gives you access to the full power of Fauna.

      To get started, log into Fauna’s dashboard. After logging in, click on the Create Database button at the top.

      In the New Database form, use PYTHON_API for the database name:

      Creating a Fauna database

      Leave Pre-populate with demo data unchecked. Press the Save button.

      After creating the database, you will see the home section for your database:

      Fauna database home

      You’re now going to create two collections:

      • The Users collection that will store documents with authentication information.
      • The Things collection to store some mock data to test your API.

      To create these collections, you’ll execute some FQL queries in the dashboard’s shell. Access the shell from the main dashboard menu on the left:

      Fauna dashboard shell

      Write the following FQL query in the bottom panel of the shell to create a collection called Things by using the CreateCollection function:

      CreateCollection({name: "Things"})
      

      Press the RUN QUERY button. You will get a result similar to this in the shell’s top panel:

      {
        ref: Collection("Things"),
        ts: 1614805457170000,
        history_days: 30,
        name: "Things"
      }
      

      The result shows four fields:

      • ref is a reference to the collection itself.
      • ts is the timestamp of its creation in microseconds.
      • history_days is how long Fauna will retain changes on documents’ changes.
      • name is the collection name.

      Next, create the Users collection with the following query:

      CreateCollection({name: "Users"})
      

      Now that both collections are in place, you will create your first document.

      Documents in Fauna are somewhat similar to JSON objects. Documents can store strings, numbers, and arrays, but they can also use Fauna data types. A common Fauna type is Ref, which represents a reference to a document in a collection.

      The Create function creates a new document into the specified collection. Run the following query to create a document in the Things collection with two fields:

      Create(
        Collection("Things"),
        {
          data: {
            name: "Banana",
            color: "Yellow"
          }
        }
      )
      

      After running that query, Fauna will return the created document:

      {
        ref: Ref(Collection("Things"), "292079274901373446"),
        ts: 1614807352895000,
        data: {
          name: "Banana",
          color: "Yellow"
        }
      }
      

      The result shows the following fields:

      • ref of type Ref is a reference to this document in the Things collection with the ID 292079274901373446. Do note that your document will have a different ID.
      • ts is the timestamp of its creation in microseconds.
      • data is the actual content of the document.

      This result looks similar to the result you got when you created a collection. That’s because all entities in Fauna (collections, indexes, roles, etc) are actually stored as documents.

      To read documents, use the Get function which accepts a reference of a document. Run the Get query using the reference for your document:

      Get(Ref(Collection("Things"), "292079274901373446"))
      

      The result is the full document:

      {
        ref: Ref(Collection("Things"), "292079274901373446"),
        ts: 1614807352895000,
        data: {
          name: "Banana",
          color: "Yellow"
        }
      }
      

      To get all references for documents stored in a collection, use the Documents function with the Paginate function:

      Paginate(Documents(Collection("Things")))
      

      This query returns a page with an array of references:

      {
        data: [Ref(Collection("Things"), "292079274901373446")]
      }
      

      To get actual documents instead of references, iterate over the references using Map. Then use a Lambda (an anonymous function) to iterate over the array of references and Get each reference:

      Map(
        Paginate(Documents(Collection("Things"))),
        Lambda("ref", Get(Var("ref")))
      )
      

      The result is an array containing full documents:

      {
        data: [
          {
            ref: Ref(Collection("Things"), "292079274901373446"),
            ts: 1614807352895000,
            data: {
              name: "Banana",
              color: "Yellow"
            }
          }
        ]
      }
      

      You’re now going to create the Users_by_username index. You typically use indexes in Fauna to catalog, filter, and sort data, but you can also use them for other purposes like enforcing unique constraints.

      The Users_by_username index will find users by their username, and also enforce a unique constraint to prevent two documents from having the same username.

      Execute this code in the shell to create the index:

      CreateIndex({
        name: "Users_by_username",
        source: Collection("Users"),
        terms: [{ field: ["data", "username"] }],
        unique: true
      })
      

      The CreateIndex function will create an index with the configured settings:

      • name is the name of the index.
      • source is the collection (or collections) the index will index data from.
      • terms is the search/filter terms you’ll pass to this index when using it to find documents.
      • unique means that the indexed values will be unique. In this example, the username property of the documents in the Users collection will be enforced as unique.

      To test the index, create a new document inside the Users collection by running the following code in the Fauna shell:

      Create(
        Collection("Users"),
        {
          data: {
            username: "sammy"
          }
        }
      )
      

      You’ll see a result like the following:

      {
        ref: Ref(Collection("Users"), "292085174927098368"),
        ts: 1614812979580000,
        data: {
          username: "sammy"
        }
      }
      

      Now try to create a document with the same username value:

      Create(
        Collection("Users"),
        {
          data: {
            username: "sammy"
          }
        }
      )
      

      You’ll receive an error now:

      Error: [
        {
          "position": [
            "create"
          ],
          "code": "instance not unique",
          "description": "document is not unique."
        }
      ]
      

      Now that the index is in place, you can query it and fetch a single document. Run this code in the shell to fetch the sammy user using the index:

      Get(
        Match(
          Index("Users_by_username"),
          "sammy"
        )
      )
      

      Here’s how it works:

      • Index returns a reference to the Users_by_username index.
      • Match returns a reference to the matched document (the one that has a username with the value of sammy).
      • Get takes the reference returned by Match, and fetches the actual document.

      The result of this query will be:

      {
        ref: Ref(Collection("Users"), "292085174927098368"),
        ts: 1614812979580000,
        data: {
          username: "sammy"
        }
      }
      

      Delete this testing document by passing its reference to the Delete function:

      Delete(Ref(Collection("Users"), "292085174927098368"))
      

      Next you’ll configure security settings for Fauna so you can connect to it from your code.

      Step 2 — Configuring a Server Key and Authorization Rules

      In this step you’ll create a server key that your Python application will use to communicate with Fauna. Then you’ll configure access permissions.

      To create a key, go to the Security section of the Fauna dashboard by using the main menu on the left. Once there:

      1. Press the New Key button.
      2. Select the Server role.
      3. Press Save.

      Creating a Fauna key

      After saving, the dashboard will show you the key’s secret. Save the secret somewhere safe and never commit it to your Git repository.

      Warning: The Server role is omnipotent and anyone with this secret would have full access to your database. As its name implies, this is the role typically used by trusted server applications, although it is also possible to create a key with a custom role with limited privileges. When you create production applications, you’ll want to make a more restrictive role.

      By default, everything in Fauna is private, so you’re now going to create a new role to allow the logged-in users to read documents from the Things collection.

      In the Security section of the dashboard, go to Roles, and create a new custom role with the name User.

      In the Collections dropdown, add the Things collection and press the Read permission so that it shows a green check mark:

      Configuring the permissions of a Fauna role

      Before saving the role, go to the Membership tab and add the Users collection to the role:

      Configuring the memerbship of a Fauna role

      You can now save your User custom role by pressingthe Save button.

      Now any logged-in user from a document in the Users collection will be able to read any document from the Things collection.

      With authentication and authorization in place, let’s now create the Python API that will talk to Fauna.

      Step 3 — Building the Python Application

      In this step you will build a small REST API using the Flask framework, and you’ll write FQL queries in Python, connecting to your Fauna database using the the Fauna driver.

      To get started, create a project folder and access it from your terminal.

      First install Flask with:

      Then install the Fauna Python driver with:

      In your project folder, create the file main.py and add the following code to the file, which adds the necessary imports, the FAUNA_SECRET environment variable, and the basic configuration of the Flask application:

      main.py

      import os
      FAUNA_SECRET = os.environ.get('FAUNA_SECRET')
      
      import flask
      from flask import request
      
      import faunadb
      from faunadb import query as q
      from faunadb.client import FaunaClient
      
      app = flask.Flask(__name__)
      app.config["DEBUG"] = True
      

      The FAUNA_SECRET environment variable will carry the server secret you created earlier. To be able to run this application, locally or in the cloud, this variable needs to be injected. Without it, the application won’t be able to connect to Fauna. You’ll provide this environment variable when you launch the app.

      Now add the the /signup route to the main.py file. This will create new documents in the Users collection:

      main.py

      @app.route('/signup', methods=['POST'])
      def signup():
      
          body = request.json
          client = FaunaClient(secret=FAUNA_SECRET)
      
          try:
              result = client.query(
                  q.create(
                      q.collection("Users"),
                      {
                          "data": {
                              "username": body["username"]
                          },
                          "credentials": {
                              "password": body["password"]
                          }
                      }
                  )
              )
      
              return {
                  "userId": result['ref'].id()
              }
      
          except faunadb.errors.BadRequest as exception:
              error = exception.errors[0]
              return {
                  "code": error.code,
                  "description": error.description
              }, 409
      

      Note that the Fauna client is being instantiated on every request using the server secret:

      main.py

      ...
      client = FaunaClient(secret=FAUNA_SECRET)
      ...
      

      Once users are logged in, the API will execute queries on behalf of each user using different secrets, which is why it makes sense to instantiate the client on every request.

      Unlike other databases, the Fauna client does not maintain a persistent connection. From the outside world, Fauna behaves like an API; every query is a single HTTP request.

      After the client is ready, the FQL query executes, which creates a new document in the Users collection. Each Fauna driver translates idiomatic syntax to FQL statements. In this route, you added this query:

      main.py

      ...
      q.create(
          q.collection("Users"),
          {
              "data": {
                  "user": json["user"]
              },
              "credentials": {
                  "password": json["password"]
              }
          }
      )
      ...
      

      This is what this query would look like in native FQL:

      Create(
          Collection("Users"),
          {
              "data": {
                  "user": "sammy"
              },
              "credentials": {
                  "password": "secretpassword"
              }
          }
      )
      

      In addition to the document data, you’re adding a credentials configuration with the user’s password. This part of the document is completely private. You will never be able to read a document’s credentials afterwards. When using Fauna’s authentication system, it’s not possible to expose users’ passwords by mistake.

      Finally, if there’s already a user with the same username, a faunadb.errors.BadRequest exception will be raised, and a 409 response with the error information will be returned to the client.

      Next, add the /login route in the main.py file to authenticate the user and password. This follows a similar pattern as the previous example; you execute a query using the Fauna connection and if the authentication fails, you raise a faunadb.errors.BadRequest exception and return a a 401 response with the error information. Add this code to main.py:

      main.py

      @app.route('/login', methods=['POST'])
      def login():
      
          body = request.json
          client = FaunaClient(secret=FAUNA_SECRET)
      
          try:
              result = client.query(
                  q.login(
                      q.match(
                          q.index("Users_by_username"),
                          body["username"]
                      ),
                      {"password": body["password"]}
                  )
              )
      
              return {
                  "secret": result['secret']
              }
      
          except faunadb.errors.BadRequest as exception:
              error = exception.errors[0]
              return {
                  "code": error.code,
                  "description": error.description
              }, 401
      

      This is the FQL query used to authenticate users with Fauna:

      main.py

      q.login(
          q.match(
              q.index("Users_by_username"),
              body["username"]
          ),
          {"password": body["password"]}
      )
      

      This is what this query would look like in native FQL:

      Login(
          Match(
              Index("Users_by_username"),
              "sammy"
          ),
          {"password": "secretpassword"}
      )
      

      Match returns a reference to a document using the Users_by_username index that we created previously.

      If the provided password matches the referenced document, Login will create a new token and return a dictionary with the following keys:

      • ref with a reference to the token for the new document.
      • ts with the timestamp of the transaction.
      • instance with a reference to the document that was used to do the authentication.
      • secret with the token’s secret that will be used to make further queries to Fauna.

      If you run that FQL query into your Fauna dashboard’s shell you will see something similar to this:

      {
        ref: Ref(Ref("tokens"), "292001047221633538"),
        ts: 1614732749110000,
        instance: Ref(Collection("Users"), "291901454585692675"),
        secret: "fnEEDWVnxbACAgQNBIxMIAIIKq1E5xvPPdGwQ_zUFH4F5Dl0neg"
      }
      

      Depending on the security requirements of the project, you have to decide how to handle the token’s secret. If this API was meant to be consumed by browsers, you might return the secret inside a secure cookie or an encrypted JSON Web Token (JWT). Or you might store it as session data somewhere else, like a Redis instance. For the purpose of this demo, you return it in the body of the HTTP response:

      Finally, add this bit of code to main.py, which will start the Flask application:

      main.py

      app.run(host=os.getenv('IP', '0.0.0.0'), port=int(os.getenv('PORT', 8080)))
      

      It’s important to specify the 0.0.0.0 address. Once deployed to the cloud, this application will run in a Docker container. It won’t be able to receive requests from remote clients if it is running on 127.0.0.1, which is the default address for Flask applications.

      This is the complete main.py file so far:

      main.py

      import os
      FAUNA_SECRET = os.environ.get('FAUNA_SECRET')
      
      import flask
      from flask import request
      
      import faunadb
      from faunadb import query as q
      from faunadb.client import FaunaClient
      
      app = flask.Flask(__name__)
      app.config["DEBUG"] = True
      
      @app.route('/signup', methods=['POST'])
      def signup():
      
          body = request.json
          client = FaunaClient(secret=FAUNA_SECRET)
      
          try:
              result = client.query(
                  q.create(
                      q.collection("Users"),
                      {
                          "data": {
                              "username": body["username"]
                          },
                          "credentials": {
                              "password": body["password"]
                          }
                      }
                  )
              )
      
              return {
                  "userId": result['ref'].id()
              }
      
          except faunadb.errors.BadRequest as exception:
              error = exception.errors[0]
              return {
                  "code": error.code,
                  "description": error.description
              }, 409
      
      @app.route('/login', methods=['POST'])
      def login():
      
          body = request.json
          client = FaunaClient(secret=FAUNA_SECRET)
      
          try:
              result = client.query(
                  q.login(
                      q.match(
                          q.index("Users_by_username"),
                          body["username"]
                      ),
                      {"password": body["password"]}
                  )
              )
      
              return {
                  "secret": result['secret']
              }
      
          except faunadb.errors.BadRequest as exception:
              error = exception.errors[0]
              return {
                  "code": error.code,
                  "description": error.description
              }, 401
      
      app.run(host=os.getenv('IP', '0.0.0.0'), port=int(os.getenv('PORT', 8080)))
      

      Save the file.

      To launch this server locally from your terminal, use the following command with the FAUNA_SECRET environment variable with the secret you obtained when creating the server key:

      • FAUNA_SECRET=your_fauna_server_secret python main.py

      After triggering that command, Flask will show a warning informing you it is running with a development WSGI server. This is fine for the purpose of this demo so you can safely ignore this warning.

      Test your API by making HTTP requests using the curl command. Open a new terminal window and run the following command:

      Create a user with the following command:

      • curl -i -d '{"user":"sammy", "password": "secretpassword"}' -H 'Content-Type: application/json' -X POST http://0.0.0.0:8080/signup

      You’ll see the following response, indicating a successful user creation:

      HTTP/1.0 200 OK
      Content-Type: application/json
      Content-Length: 37
      Server: Werkzeug/1.0.1 Python/3.9.2
      Date: Thu, 04 Mar 2021 01:00:47 GMT
      
      {
        "userId": "292092166117786112"
      }
      

      Now authenticate that user with this command:

      • curl -i -d '{"user":"sammy", "password": "secretpassword"}' -H 'Content-Type: application/json' -X POST http://0.0.0.0:8080/login

      You’ll get this successful response:

      HTTP/1.0 200 OK
      Content-Type: application/json
      Content-Length: 70
      Server: Werkzeug/1.0.1 Python/3.9.2
      Date: Thu, 04 Mar 2021 01:01:19 GMT
      
      {
        "secret": "fnEEDbhO3jACAAQNBIxMIAIIOlDxujk-VJShnnhkZkCUPKIHxbc"
      }
      

      Close the terminal window where you ran your curl commands and switch back to the terminal where your Python server is running. Stop your server by pressing CTRL+C.

      Now that the application is working, we’re going to add a private endpoint that requires users to be authenticated.

      Step 4 — Adding a Private Endpoint

      In this step, you’ll add a private endpoint to the API, which will require the user to be authenticated first.

      First, create a new route in the main.py file. This route will respond to the /things endpoint. Place it above the line that starts the server with the app.run() method:

      main.py

      @app.route('/things', methods=['GET'])
      def things():
      

      Next, in the /things route, instantiate the Fauna client:

      main.py

          userSecret = request.headers.get('fauna-user-secret')
          client = FaunaClient(secret=userSecret)
      

      Instead of using the server secret, this route is using the user’s secret from the fauna-user-secret HTTP header which is used to instantiate the Fauna client. By using the users’ secrets instead of the server secret, FQL queries will now be subject to the authorization rules we’ve configured previously in the dashboard.

      Then add this try block to the route to execute the query:

      main.py

          try:
              result = client.query(
                  q.map_(
                      q.lambda_("ref", q.get(q.var("ref"))),
                      q.paginate(q.documents(q.collection("Things")))
                  )
              )
      
              things = map(
                  lambda doc: {
                      "id": doc["ref"].id(),
                      "name": doc["data"]["name"],
                      "color": doc["data"]["color"]
                  },
                  result["data"]
              )
      
              return {
                  "things": list(things)
              }
      

      This executes an FQL query and parses the Fauna response into a serializable type that is then returned as a JSON string in the body of the HTTP response.

      Finally, add this except block to the route:

      main.py

          except faunadb.errors.Unauthorized as exception:
              error = exception.errors[0]
              return {
                  "code": error.code,
                  "description": error.description
              }, 401
      

      If the request doesn’t contain a valid secret, a faunadb.errors.Unauthorized exception will be raised and a 401 response with the error information will be returned.

      This is the full code for the /things route:

      main.py

      @app.route('/things', methods=['GET'])
      def things():
      
          userSecret = request.headers.get('fauna-user-secret')
          client = FaunaClient(secret=userSecret)
      
          try:
              result = client.query(
                  q.map_(
                      q.lambda_("ref", q.get(q.var("ref"))),
                      q.paginate(q.documents(q.collection("Things")))
                  )
              )
      
              things = map(
                  lambda doc: {
                      "id": doc["ref"].id(),
                      "name": doc["data"]["name"],
                      "color": doc["data"]["color"]
                  },
                  result["data"]
              )
      
              return {
                  "things": list(things)
              }
      
          except faunadb.errors.Unauthorized as exception:
              error = exception.errors[0]
              return {
                  "code": error.code,
                  "description": error.description
              }, 401
      

      Save the file and run your server again:

      • FAUNA_SECRET=your_fauna_server_secret python main.py

      To test this endpoint, first obtain a secret by authenticating with valid credentials. Open a new terminal window and execute this curl command:

      • curl -i -d '{"username":"sammy", "password": "secretpassword"}' -H 'Content-Type: application/json' -X POST http://0.0.0.0:8080/login

      This command returns a succesful response, although the value for secret will be different:

      HTTP/1.0 200 OK
      Content-Type: application/json
      Content-Length: 70
      Server: Werkzeug/1.0.1 Python/3.9.2
      Date: Thu, 04 Mar 2021 01:01:19 GMT
      
      {
        "secret": "fnEEDb...."
      }
      

      Now hen do a GET request to /things using the secret:

      curl -i -H 'fauna-user-secret: fnEEDb...' -X GET http://0.0.0.0:8080/things
      

      You’ll get another successful response:

      HTTP/1.0 200 OK
      Content-Type: application/json
      Content-Length: 118
      Server: Werkzeug/1.0.1 Python/3.9.2
      Date: Thu, 04 Mar 2021 01:14:49 GMT
      
      {
        "things": [
          {
            "color": "Yellow",
            "id": "292079274901373446",
            "name": "Banana"
          }
        ]
      }
      

      Close your terminal window where you ran the curl commands. Return to your window where your server is running and stop the server with CTRL+C.

      Now that you have a working app, you’re ready to deploy it.

      Step 4 — Deploying to DigitalOcean

      In the final step of this tutorial, you will create an app on App Platform and deploy it from a GitHub repository.

      Before pushing the project to a Git repository, be sure to run the following command in the project’s folder:

      • pip freeze > requirements.txt

      This will create a requirements.txt file with the list of dependencies that need to be installed once the application is deployed.

      Now initialize your project directory as a Git repository:

      Now execute the following command to add files to your repository:

      This adds all the files in the current directory.

      With the files added, make your initial commit:

      • git commit -m "Initial version of the site"

      Your files will commit.

      Open your browser and navigate to GitHub, log in with your profile, and create a new repository called sharkopedia. Create an empty repository without a README or license file.

      Once you’ve created the repository, return to the command line to push your local files to GitHub.

      First, add GitHub as a remote repository:

      • git remote add origin https://github.com/your_username/sharkopedia

      Next, rename the default branch main, to match what GitHub expects:

      Finally, push your main branch to GitHub’s main branch:

      Your files will transfer. You’re now ready to deploy your app.

      Note: To be able to create an app on App Platform, you’ll first need to add a payment method to your DigitalOcean account.

      The application will run on a container which costs $5 per month, although only a few cents will be needed to test it out. Don’t forget to delete the application once you’re done or you’ll continue to be charged.

      Go to the Apps section of the DigitalOcean dashboard, and click on Launch Your App:

      Select the source for deployment. You will need to authorize DigitalOcean to read your Github repositories. Once you’ve authorized access, select the repository with your Python project and the branch that contains the version of the app you want to deploy:

      Selecting a repository and branch

      At this point, App Platform will determine that your project uses Python and will let you configure some application options:

      Configuring the app options

      Set the following options

      • Ensure the Type is Web Service.
      • Create aFAUNA_SECRET environment variable with your server secret.
      • Set the Run Command to python main.py.
      • Set the HTTP Port to 8080.

      Next, enter a name for your app and select a deploy region:

      Configuring the name of the app and deploy region

      Next, choose the Basic plan and Basic Size that costs $5 per month:

      Selecting the app plan

      After that, scroll down and click on Launch Your App.

      Once you’ve finished configuring the app, a container will be created and deployed with your application. This first-time initialization will take a couple of minutes, but subsequent deploys will be much faster.

      In the app’s dashboard you’ll see a green check mark to indicate the deploy process has finished successfully:

      App is running

      You will now be able to execute HTTP requests to the provided app domain. Execute the following command in your terminal, substituting your_app_name with your actual app name, to return a new secret for the sammy user:

      • curl -i -d '{"user":"sammy", "password": "secretpassword"}' -H 'Content-Type: application/json' -X POST https://your_app_name.ondigitalocean.app/login

      You’ll receive a response similar to the following:

      HTTP/1.0 200 OK
      Content-Type: application/json
      Content-Length: 70
      Server: Werkzeug/1.0.1 Python/3.9.2
      Date: Thu, 04 Mar 2021 01:01:19 GMT
      
      {
        "secret": "fnAADbhO3jACEEQNBIxMIAOOIlDxujk-VJShnnhkZkCUPKIskdjfh"
      }
      

      Your application is now up and running on Digital Ocean.

      Conclusion

      In this tutorial you created a Python REST API using Fauna as the data layer, and you deployed it to DigitalOcean App Platform.

      To keep learning about Fauna and dive deeper into FQL, check out the Fauna Documentation.



      Source link

      How To Set Up WireGuard on Rocky Linux 8


      Not using Rocky Linux 8?


      Choose a different version or distribution.

      Introduction

      WireGuard is a lightweight Virtual Private Network (VPN) that supports IPv4 and IPv6 connections. A VPN allows you to traverse untrusted networks as if you were on a private network. It gives you the freedom to access the internet safely and securely from your smartphone or laptop when connected to an untrusted network, like the WiFi at a hotel or coffee shop.

      WireGuard’s encryption relies on public and private keys for peers to establish an encrypted tunnel between themselves. Each version of WireGuard uses a specific cryptographic cipher suite to ensure simplicity, security, and compatibility with peers.

      In comparison, other VPN software such as OpenVPN and IPSec use Transport Layer Security (TLS) and certificates to authenticate and establish encrypted tunnels between systems. Different versions of TLS include support for hundreds of different cryptographic suites and algorithms, and while this allows for great flexibility to support different clients, it also makes configuring a VPN that uses TLS more time consuming, complex, and error prone.

      In this tutorial, you will set up WireGuard on an Rocky Linux 8 server, and then configure another machine to connect to it as a peer using both IPv4 and IPv6 connections (commonly referred to as a dual stack connection). You’ll also learn how to route the peer’s Internet traffic through the WireGuard server in a gateway configuration, in addition to using the VPN for an encrypted peer-to-peer tunnel.

      For the purposes of this tutorial, we’ll configure another Rocky Linux 8 system as the peer (also referred to as client) to the WireGuard Server. Subsequent tutorials in this series will explain how to install and run WireGuard on Windows, macOS, Android, and iOS systems and devices.

      Note: If you plan to set up WireGuard on a DigitalOcean Droplet, be aware that we, like many hosting providers, charge for bandwidth overages. For this reason, please be mindful of how much traffic your server is handling.
      See this page for more info.

      Prerequisites

      To follow this tutorial, you will need:

      • One Rocky Linux 8 server with a sudo non-root user and a firewall enabled. To set this up, you can follow our Initial Server Setup with Rocky Linux 8 tutorial. We will refer to this as the WireGuard Server throughout this guide.
      • You’ll need a client machine that you will use to connect to your WireGuard Server. In this tutorial we’ll refer to this machine as the WireGuard Peer. For the purposes of this tutorial, it’s recommended that you use your local machine as the WireGuard Peer, but you can use remote servers, or mobile phones as clients if you prefer. If you are using a remote system, be sure to follow all of the optional sections later in this tutorial or you may lock yourself out of the system.
      • To use WireGuard with IPv6, you will also need to ensure that your server is configured to support that type of traffic. If you would like to enable IPv6 support with WireGuard and are using a DigitalOcean Droplet, please refer to this documentation page How to Enable IPv6 on Droplets
        . You can add IPv6 support when you create a Droplet, or afterwards using the instructions on that page.

      Step 1 — Installing WireGuard and Generating a Key Pair

      The first step in this tutorial is to install WireGuard on your server. To start off, you’ll need to add two extra software repositories to your server’s package index, epel, and elrepo. Run the following command to install them. Note that you may be prompted to provide your sudo user’s password if this is the first time you’re using sudo in this session:

      • sudo dnf install elrepo-release epel-release

      Now that your server can access the repositories that host the WireGuard packages, install WireGuard using the following commands:

      • sudo dnf install kmod-wireguard wireguard-tools

      Now that you have WireGuard installed, the next step is to generate a private and public keypair for the server. You’ll use the built-in wg genkey and wg pubkey commands to create the keys, and then add the private key to WireGuard’s configuration file.

      Because you’ll be creating a private key that will be used to encrypt traffic on your WireGuard Server, the default permissions that are applied to new files need to be temporarily changed to a more restrictive value before this file is created. You’ll need to set the default permissions for newly created files using the umask command.

      Use the following umask command to ensure new directories and files (in your current terminal session only) get created with limited read and write permissions:

      A umask of 077 will ensure that only the owner of a directory can enter into it, and only the owner of a file can read or write to it. Again note that when you exit your shell and log back in, your umask will be reset to the default 022 value, which allows read access to any new files created on the system.

      Now you can proceed and create the private key for WireGuard using the following command:

      • wg genkey | sudo tee /etc/wireguard/private.key

      You should receive a single line of base64 encoded output, which is the private key. A copy of the output is also stored in the /etc/wireguard/private.key file for future reference by the tee portion of the command. Carefully make a note of the private key that is output since you’ll need to add it to WireGuard’s configuration file later in this section.

      The next step is to create the corresponding public key, which is derived from the private key. Use the following command to create the public key file:

      • sudo cat /etc/wireguard/private.key | wg pubkey | sudo tee /etc/wireguard/public.key

      This command consists of three individual commands that are chained together using the | (pipe) operator:

      • sudo cat /etc/wireguard/private.key: this command reads the private key file and outputs it to the standard output stream.
      • wg pubkey: the second command takes the output from the first command as its standard input and processes it to generate a public key.
      • sudo tee /etc/wireguard/public.key: the final command takes the output of the public key generation command and redirects it into the file named /etc/wireguard/public.key.

      When you run the command you will again receive a single line of base64 encoded output, which is the public key for your WireGuard Server. Copy it somewhere for reference, since you will need to distribute the public key to any peer that connects to the server.

      Step 2 — Choosing IPv4 and IPv6 Addresses

      In the previous section you installed WireGuard and generated a key pair that will be used to encrypt traffic to and from the server. In this section, you will create a configuration file for the server, and set up WireGuard to start up automatically when you server reboots. You will also define private IPv4 and IPv6 addresses to use with your WireGuard Server and peers.

      If you plan to use both IPv4 and IPv6 addresses then follow both of these sections. Otherwise, follow the instructions in the appropriate section for your VPN’s network needs.

      Step 2(a) — Choosing an IPv4 Range

      If you are using your WireGuard server with IPv4 peers, the server needs a range of private IPv4 addresses to use for clients, and for its tunnel interface. You can choose any range of IP addresses from the following reserved blocks of addresses (if you would like to learn more about how these blocks are allocated visit the RFC 1918 specification):

      • 10.0.0.0 to 10.255.255.255 (10/8 prefix)
      • 172.16.0.0 to 172.31.255.255 (172.16/12 prefix)
      • 192.168.0.0 to 192.168.255.255 (192.168/16 prefix)

      For the purposes of this tutorial we’ll use 10.8.0.0/24 as a block of IP addresses from the first range of reserved IPs. This range will allow up to 255 different peer connections, and generally should not have overlapping or conflicting addresses with other private IP ranges. Feel free to choose a range of addresses that works with your network configuration if this example range isn’t compatible with your networks.

      The WireGuard Server will use a single IP address from the range for its private tunnel IPv4 address. We’ll use 10.8.0.1/24 here, but any address in the range of 10.8.0.1 to 10.8.0.255 can be used. Make a note of the IP address that you choose if you use something different from 10.8.0.1/24. You will add this IPv4 address to the configuration file that you define in Step 3 — Creating a WireGuard Server Configuration
      .

      Step 2(b) — Choosing an IPv6 Range

      If you are using WireGuard with IPv6, then you will need to generate a unique local IPv6 unicast address prefix based on the algorithm in RFC 4193. The addresses that you use with WireGuard will be associated with a virtual tunnel interface. You will need to complete a few steps to generate a random, unique IPv6 prefix within the reserved fd00::/8 block of private IPv6 addresses.

      According to the RFC, the recommended way to obtain a unique IPv6 prefix is to combine the time of day with a unique identifying value from a system like a serial number or device ID. Those values are then hashed and truncated resulting in a set of bits that can be used as a unique address within the reserved private fd00::/8 block of IPs.

      To get started generating an IPv6 range for your WireGuard Server, collect a 64-bit timestamp using the date utility with the following command:

      You will receive a number like the following, which is the number of seconds (the %s in the date command), and nanoseconds (the %N) since 1970-01-01 00:00:00 UTC combined together:

      Output

      1628101352127592197

      Record the value somewhere for use later in this section. Next, copy the machine-id value for your server from the /var/lib/dbus/machine-id file. This identifier is unique to your system and should not change for as long as the server exists.

      • cat /var/lib/dbus/machine-id

      You will receive output like the following:

      /var/lib/dbus/machine-id

      20086c25853947c7aeee2ca1ea849d7d

      Now you need to combine the timestamp with the machine-id and hash the resulting value using the SHA-1 algorithm. The command will use the following format:

      printf <timestamp><machine-id> | sha1sum
      

      Run the command substituting in your timestamp and machine identity values:

      • printf 162810135212759219720086c25853947c7aeee2ca1ea849d7d | sha1sum

      You will receive a hash value like the following:

      Output

      4f267c51857d6dc93a0bca107bca2f0d86fac3bc -

      Note that the output of the sha1sum command is in hexadecimal, so the output uses two characters to represent a single byte of data. For example 4f and 26 in the example output are the first two bytes of the hashed data.

      The algorithm in the RFC only requires the least significant (trailing) 40 bits, or 5 bytes, of the hashed output. Use the cut command to print the last 5 hexadecimal encoded bytes from the hash:

      • printf 4f267c51857d6dc93a0bca107bca2f0d86fac3bc | cut -c 31-

      The -c argument tells the cut command to select only a specified set of characters. The 31- argument tells cut to print all the characters from position 31 to the end of the input line.

      You should receive output like the following:

      Output

      0d86fac3bc

      In this example output, the set of bytes is: 0d 86 fa c3 bc.

      Now you can construct your unique IPv6 network prefix by appending the 5 bytes you have generated with the fd prefix, separating every 2 bytes with a : colon for readability. Because each subnet in your unique prefix can hold a total of 18,446,744,073,709,551,616 possible IPv6 addresses, you can restrict the subnet to a standard size of /64 for simplicity.

      Using the bytes previously generated with the /64 subnet size the resulting prefix will be the following:

      Unique Local IPv6 Address Prefix

      fd0d:86fa:c3bc::/64

      This fd0d:86fa:c3bc::/64 range is what you will use to assign individual IP addresses to your WireGuard tunnel interfaces on the server and peers. To allocate an IP for the server, add a 1 after the final :: characters. The resulting address will be fd0d:86fa:c3bc::1/64. Peers can use any IP in the range, but typically you’ll increment the value by one each time you add a peer e.g. fd0d:86fa:c3bc::2/64. Make a note of the IP and proceed configuring the WireGuard Server in the next section of this tutorial.

      Step 3 — Creating a WireGuard Server Configuration

      Before creating your WireGuard Server’s configuration, you will need the following pieces of information:

      1. Make sure that you have the private key available from Step 1 — Installing WireGuard and Generating a Key Pair.

      2. If you are using WireGuard with IPv4, you’ll need the IP address that you chose for the server in Step 2(a) — Choosing an IPv4 Range, which in this example is 10.8.0.1/24.

      3. If you are using WireGuard with IPv6, you’ll need the IP address for the server that you generated in Step 2(b) — Choosing an IPv6 Range. In this example the IP is fd0d:86fa:c3bc::1/64.

      Once you have the required private key and IP address(es), create a new configuration file using vi or your preferred editor by running the following command:

      • sudo vi /etc/wireguard/wg0.conf

      Add the following lines to the file, substituting your private key in place of the highlighted base64_encoded_private_key_goes_here value, and the IP address(es) on the Address line. You can also change the ListenPort line if you would like WireGuard to be available on a different port.

      Press i to put vi into insertion mode and then add the following lines:

      /etc/wireguard/wg0.conf

      [Interface] PrivateKey = base64_encoded_private_key_goes_here Address = 10.8.0.1/24, fd0d:86fa:c3bc::1/64 ListenPort = 51820 SaveConfig = true

      The SaveConfig line ensures that when a WireGuard interface is shutdown, any changes will get saved to the configuration file.

      When you are finished making changes, press ESC and then :wq to write the changes to the file and quit. You now have an initial server configuration that you can build upon depending on how you plan to use your WireGuard VPN server.

      Step 4 — Adjusting the WireGuard Server’s Network Configuration

      If you are using WireGuard to connect a peer to the WireGuard Server in order to access services on the server only, then you do not need to complete this section. If you would like to route your WireGuard Peer’s Internet traffic through the WireGuard Server then you will need to configure IP forwarding by following this section of the tutorial.

      To configure forwarding, open the /etc/sysctl.conf file using vi or your preferred editor:

      If you are using IPv4 with WireGuard, add the following line at the bottom of the file:

      /etc/sysctl.conf

      net.ipv4.ip_forward=1
      

      If you are using IPv6 with WireGuard, add this line at the bottom of the file:

      /etc/sysctl.conf

      net.ipv6.conf.all.forwarding=1
      

      If you are using both IPv4 and IPv6, ensure that you include both lines. Save and close the file when you are finished.

      To read the file and load the new values for your current terminal session, run:

      Output

      net.ipv6.conf.all.forwarding = 1 net.ipv4.ip_forward = 1

      Now your WireGuard Server will be able to forward incoming traffic from the virtual VPN ethernet device to others on the server, and from there to the public Internet. Using this configuration will allow you to route all web traffic from your WireGuard Peer via your server’s IP address, and your client’s public IP address will be effectively hidden.

      However, before traffic can be routed via your server correctly, you will need to configure some firewall rules. These rules will ensure that traffic to and from your WireGuard Server and Peers flows properly.

      Step 5 — Configuring the WireGuard Server’s Firewall

      In this section you will edit the WireGuard Server’s configuration to add firewall-cmd firewall rules that will ensure traffic to and from the server and clients is routed correctly. As with the previous section, skip this step if you are only using your WireGuard VPN for a machine to machine connection to access resources that are restricted to your VPN.

      To add firewall rules to your WireGuard Server, you will create some permanent rules that will ensure the server is configured correctly across reboots. Run the following to allow access to the WireGuard service itself on UDP port 51820:

      • sudo firewall-cmd --zone=public --add-port=51820/udp --permanent

      Next you will need to add the wg0 device to the internal zone, which will allow traffic on the VPN interface to reach other interfaces on the WireGuard Server. This setting is particularly important if you are using the Server as a VPN gateway for all the Peer’s Internet traffic. If you add more WireGuard tunnels to your server in the future, be sure to add their devices to the internal or trusted zone as well.

      Run the following to add the wg0 interface to the internal zone:

      • sudo firewall-cmd --zone=internal --add-interface=wg0 --permanent

      Finally, if you are using the WireGuard Server as a VPN gateway, you will need to add a masquerade rule to the public zone. Masquerading is used to rewrite traffic that comes in on an internal interface (in this case wg0) to make it appear like it originates directly from the WireGuard Server’s public IPv4 or IPv6 addresses.

      Run the following commands to enable masquerading, substituting in your IPv4 and IPv6 network ranges in place of the highlighted values:

      • sudo firewall-cmd --zone=public --add-rich-rule="rule family=ipv4 source address=10.8.0.0/24 masquerade" --permanent
      • sudo firewall-cmd --zone=public --add-rich-rule="rule family=ipv6 source address=fd0d:86fa:c3bc::/64 masquerade" --permanent

      Now reload the firewall to make the changes take effect, and to ensure that they are permanent:

      • sudo firewall-cmd --reload

      Note: If you are using a different firewall or have customized your firewalld configuration, you may need to add additional firewall rules. For example, if you decide to tunnel all of your network traffic over the VPN connection, you will need to ensure that port 53 traffic is allowed for DNS requests, and ports like 80 and 443 for HTTP and HTTPS traffic respectively. If there are other protocols that you are using over the VPN then you will need to add rules for them as well.

      You can now examine the status of the entire public, internal, or other firewall zones to confirm that the rules are in place by running the following command. Substitute in the zone that you would like to examine in place of the highlighted public name:

      • sudo firewall-cmd --zone=public --list-all

      You will receive output like the following:

      Output

      public (active) target: default icmp-block-inversion: no interfaces: eth0 eth1 sources: services: cockpit dhcpv6-client ssh ports: 51820/udp protocols: masquerade: no forward-ports: source-ports: icmp-blocks: rich rules: rule family="ipv4" source address="10.8.0.0/24" masquerade rule family="ipv6" source address="fd0d:86fa:c3bc::/64" masquerade

      The highlighted values indicate the rule to allow UDP traffic on port 51820 is present, and that masquerading is enabled for any of the listed networks.

      You can also verify that the internal forwarding rule is in place using the following command:

      • sudo firewall-cmd --zone=internal --list-interfaces

      You will receive output like the following if the rule is present:

      Output

      wg0

      Your WireGuard Server is now configured to correctly handle the VPN’s traffic, including forwarding and masquerading for peers. With the firewall rules in place, you can start the WireGuard service itself to listen for peer connections.

      Step 6 — Starting the WireGuard Server

      WireGuard can be configured to run as a systemd service using its built-in wg-quick script. While you could manually use the wg command to create the tunnel every time you want to use the VPN, doing so is a manual process that becomes repetitive and error prone. Instead, you can use systemctl to manage the tunnel with the help of the wg-quick script.

      Using a systemd service means that you can configure WireGuard to start up at boot so that you can connect to your VPN at any time as long as the server is running. To do this, enable the wg-quick service for the wg0 tunnel that you’ve defined by adding it to systemctl:

      • sudo systemctl enable wg-quick@wg0.service

      Notice that the command specifies the name of the tunnel wg0 as a part of the service name. This name maps to the /etc/wireguard/wg0.conf configuration file. This approach to naming means that you can create as many separate VPN tunnels as you would like using your server. Each tunnel can contain different IPv4, IPv6, and client firewall settings.

      Now start the service:

      • sudo systemctl start wg-quick@wg0.service

      Double check that the WireGuard service is active with the following command. You should see active (running) in the output:

      • sudo systemctl status wg-quick@wg0.service

      Output

      ● wg-quick@wg0.service - WireGuard via wg-quick(8) for wg0 Loaded: loaded (/usr/lib/systemd/system/wg-quick@.service; enabled; vendor preset: disabled) Active: active (exited) since Fri 2021-09-17 19:58:14 UTC; 6 days ago Docs: man:wg-quick(8) man:wg(8) https://www.wireguard.com/ https://www.wireguard.com/quickstart/ https://git.zx2c4.com/wireguard-tools/about/src/man/wg-quick.8 https://git.zx2c4.com/wireguard-tools/about/src/man/wg.8 Main PID: 22924 (code=exited, status=0/SUCCESS) Tasks: 0 (limit: 11188) Memory: 0B CGroup: /system.slice/system-wgx2dquick.slice/wg-quick@wg0.service Sep 17 19:58:14 wg0 systemd[1]: Starting WireGuard via wg-quick(8) for wg0... Sep 17 19:58:14 wg0 wg-quick[22924]: [#] ip link add wg0 type wireguard Sep 17 19:58:14 wg0 wg-quick[22924]: [#] wg setconf wg0 /dev/fd/63 Sep 17 19:58:14 wg0 wg-quick[22924]: [#] ip -4 address add 10.8.0.1/24 dev wg0 Sep 17 19:58:14 wg0 wg-quick[22924]: [#] ip -6 address add fd0d:86fa:c3bc::1/64 dev wg0 Sep 17 19:58:14 wg0 wg-quick[22924]: [#] ip link set mtu 1420 up dev wg0 Sep 17 19:58:14 wg0 systemd[1]: Started WireGuard via wg-quick(8) for wg0.

      Notice how the output shows the ip commands that are used to create the virtual wg0 device and assign it the IPv4 and IPv6 addresses that you added to the configuration file. You can use these rules to troubleshoot the tunnel, or with the wg command itself if you would like to try manually configuring the VPN interface.

      With the server configured and running, the next step is to configure your client machine as a WireGuard Peer and connect to the WireGuard Server.

      Step 7 — Configuring a WireGuard Peer

      Configuring a WireGuard peer is similar to setting up the WireGuard Server. Once you have the client software installed, you’ll generate a public and private key pair, decide on an IP address or addresses for the peer, define a configuration file for the peer, and then start the tunnel using the wg-quick script.

      You can add as many peers as you like to your VPN by generating a key pair and configuration using the following steps. If you add multiple peers to the VPN be sure to keep track of their private IP addresses to prevent collisions.

      To configure the WireGuard Peer, ensure that you have the WireGuard package installed using the following dnf commands. On the WireGuard peer run:

      • sudo dnf install elrepo-release epel-release
      • sudo dnf install kmod-wireguard wireguard-tools

      Creating the WireGuard Peer’s Key Pair

      Next, you’ll need to generate the key pair on the peer using the same steps as you used on the server. From your local machine or remote server that will serve as peer, run the following command to set the umask to 077:

      Now you can proceed and create the private key for the peer using the following command:

      • wg genkey | sudo tee /etc/wireguard/private.key

      Again you will receive a single line of base64 encoded output, which is the private key. A copy of the output is also stored in the /etc/wireguard/private.key. Carefully make a note of the private key that is output since you’ll need to add it to WireGuard’s configuration file later in this section.

      Next use the following command to create the public key file:

      • sudo cat /etc/wireguard/private.key | wg pubkey | sudo tee /etc/wireguard/public.key

      You will again receive a single line of base64 encoded output, which is the public key for your WireGuard Peer. Copy it somewhere for reference, since you will need to distribute the public key to the WireGuard Server in order to establish an encrypted connection.

      Creating the WireGuard Peer’s Configuration File

      Now that you have a key pair, you can create a configuration file for the peer that contains all the information that it needs to establish a connection to the WireGuard Server.

      You will need a few pieces of information for the configuration file:

      • The base64 encoded private key that you generated on the peer.

      • The IPv4 and IPv6 address ranges that you defined on the WireGuard Server.

      • The base64 encoded public key from the WireGuard Server.

      • The public IP address and port number of the WireGuard Server. Usually this will be the IPv4 address, but if your server has an IPv6 address and your client machine has an IPv6 connection to the internet you can use this instead of IPv4.

      With all this information at hand, open a new /etc/wireguard/wg0.conf file on the WireGuard Peer machine using vi or your preferred editor:

      • sudo vi /etc/wireguard/wg0.conf

      Add the following lines to the file, substituting in the various data into the highlighted sections as required:

      /etc/wireguard/wg0.conf

      [Interface] PrivateKey = base64_encoded_peer_private_key_goes_here Address = 10.8.0.2/24 Address = fd0d:86fa:c3bc::2/64 [Peer] PublicKey = base64_encoded_server_public_key_goes_here AllowedIPs = 10.8.0.0/24, fd0d:86fa:c3bc::/64 Endpoint = 203.0.113.1:51820

      Notice how the first Address line uses an IPv4 address from the 10.8.0.0/24 subnet that you chose earlier. This IP address can be anything in the subnet as long as it is different from the server’s IP. Incrementing addresses by 1 each time you add a peer is generally the easiest way to allocate IPs.

      Likewise, notice how the second Address line uses an IPv6 address from the subnet that you generated earlier, and increments the server’s address by one. Again, any IP in the range is valid if you decide to use a different address.

      The other notable part of the file is the last AllowedIPs line. These two IPv4 and IPv6 ranges instruct the peer to only send traffic over the VPN if the destination system has an IP address in either range. Using the AllowedIPs directive, you can restrict the VPN on the peer to only connect to other peers and services on the VPN, or you can configure the setting to tunnel all traffic over the VPN and use the WireGuard Server as a gateway.

      If you are only using IPv4, then omit the trailing fd0d:86fa:c3bc::/64 range (including the , comma). Conversely, if you are only using IPv6, then only include the fd0d:86fa:c3bc::/64 prefix and leave out the 10.8.0.0/24 IPv4 range.

      In both cases, if you would like to send all your peer’s traffic over the VPN and use the WireGuard Server as a gateway for all traffic, then you can use 0.0.0.0/0, which represents the entire IPv4 address space, and ::/0 for the entire IPv6 address space.

      (Optional) Configuring a Peer to Route All Traffic Over the Tunnel

      If you have opted to route all of the peer’s traffic over the tunnel using the 0.0.0.0/0 or ::/0 routes and the peer is a remote system, then you will need to complete the steps in this section. If your peer is a local system then it is best to skip this section.

      For remote peers that you access via SSH or some other protocol using a public IP address, you will need to add some extra rules to the peer’s wg0.conf file. These rules will ensure that you can still connect to the system from outside of the tunnel when it is connected. Otherwise, when the tunnel is established, all traffic that would normally be handled on the public network interface will not be routed correctly to bypass the wg0 tunnel interface, leading to an inaccessible remote system.

      First, you’ll need to determine the IP address that the system uses as its default gateway. Run the following ip route command:

      • ip route list table main default

      You will receive output like the following:

      Output

      default via 203.0.113.1 dev eth0 proto static

      Note the gateway’s highlighted IP address 203.0.113.1 for later use, and device eth0. Your device name may be different. If so, substitute it in place of eth0 in the following commands.

      Next find the public IP for the system by examining the device with the ip address show command:

      • ip -brief address show eth0

      You will receive output like the following:

      Output

      eth0 UP 203.0.113.5/20 10.20.30.40/16 2604:a880:400:d1::3d3:6001/64 fe80::68d5:beff:feff:974c/64

      In this example output, the highlighted 203.0.113.5 IP (without the trailing /20) is the public address that is assigned to the eth0 device that you’ll need to add to the WireGuard configuration.

      Now open the WireGuard Peer’s /etc/wireguard/wg0.conf file with vi or your preferred editor.

      • sudo vi /etc/wireguard/wg0.conf

      Before the [Peer] line, add the following 4 lines:

      PostUp = ip rule add table 200 from 203.0.113.5
      PostUp = ip route add table 200 default via 203.0.113.1
      PreDown = ip rule delete table 200 from 203.0.113.5
      PreDown = ip route delete table 200 default via 203.0.113.1
      
      [Peer]
      . . .
      

      These lines will create a custom routing rule, and add a custom route to ensure that public traffic to the system uses the default gateway.

      • PostUp = ip rule add table 200 from 203.0.113.5 - This command creates a rule that checks for any routing entries in the table numbered 200 when the IP matches the system’s public 203.0.113.5 address.
      • PostUp = ip route add table 200 default via 203.0.113.1 - This command ensures that any traffic being processed by the 200 table will use the 203.0.113.1 gateway for routing, instead of the WireGuard interface.

      The PreDown lines remove the custom rule and route when the tunnel is shutdown.

      Note: The table number 200 is arbitrary when constructing these rules. You can use a value between 2 and 252, or you can use a custom name by adding a label to the /etc/iproute2/rt_tables file and then referring to the name instead of the numeric value.

      For more information about how routing tables work in Linux visit the Routing Tables Section of the Guide to IP Layer Network Administration with Linux.

      If you are routing all the peer’s traffic over the VPN, ensure that you have configured the correct sysctl and firewall-cmd rules on the WireGuard Server in Step 4 — Adjusting the WireGuard Server’s Network Configuration and Step 5 — Configuring the WireGuard Server’s Firewall.

      (Optional) Configuring the WireGuard Peer’s DNS Resolvers

      If you are using the WireGuard Server as a VPN gateway for all your peer’s traffic, you will need to add a line to the [Interface] section that specifies DNS resolvers. If you do not add this setting, then your DNS requests may not be secured by the VPN, or they might be revealed to your Internet Service Provider or other third parties.

      If you are only using WireGuard to access resources on the VPN network or in a peer-to-peer configuration then you can skip this section.

      To add DNS resolvers to your peer’s configuration, first determine which DNS servers your WireGuard Server is using. Run the following command on the WireGuard Server, substituting in your ethernet device name in place of eth0 if it is different from this example:

      You should receive output like the following:

      Output

      ; Created by cloud-init on instance boot automatically, do not edit. ; nameserver 67.207.67.2 nameserver 67.207.67.3

      The IP addresses that are output are the DNS resolvers that the server is using. You can choose to use any or all of them, or only IPv4 or IPv6 depending on your needs. Make a note of the resolvers that you will use.

      Next you will need to add your chosen resolvers to the WireGuard Peer’s configuration file. Back on the WireGuard Peer, open /etc/wireguard/wg0.conf file using vi or your preferred editor:

      • sudo vi /etc/wireguard/wg0.conf

      Before the [Peer] line, add the following:

      DNS = 67.207.67.2 67.207.67.3
      
      [Peer]
      . . .
      

      Again, depending on your preference or requirements for IPv4 and IPv6, you can edit the list according to your needs.

      Next, enable and start the systemd-resolved service on the Peer so that when the tunnel is established, the Peer’s DNS resolvers get updated:

      • sudo systemctl enable systemd-resolved

      You will need to reboot your peer system at this point if it is running Rocky Linux, and possibly other RedHat derived distributions like CentOS or Fedora. If you do not reboot it, the /etc/resolv.conf file will not have the correct permissions set when you start the tunnel due to a bug in the systemd-resolved or wireguard-tools programs. Reboot the WireGuard Peer with the following command:

      Once you are connected to the VPN in the following step, you can check that you are sending DNS queries over the VPN by using a site like DNS leak test.com.

      You can also check that your peer is using the configured resolvers with the resolvectl dns command like you ran on the server. You should receive output like the following, showing the DNS resolvers that you configured for the VPN tunnel:

      Output

      Global: 67.207.67.2 67.207.67.3 . . .

      With all of these DNS resolver settings in place and the peer rebooted, you are now ready to add the peer’s public key to the server, and then start the WireGuard tunnel on the peer.

      Step 8 — Adding the Peer’s Public Key to the WireGuard Server

      Before connecting the peer to the server, it is important to add the peer’s public key to the WireGuard Server. This step ensures that you will be able to connect to and route traffic over the VPN. Without completing this step the WireGuard server will not allow the peer to send or receive any traffic over the tunnel.

      Ensure that you have a copy of the base64 encoded public key for the WireGuard Peer by running:

      • sudo cat /etc/wireguard/public.key

      Output

      PeURxj4Q75RaVhBKkRTpNsBPiPSGb5oQijgJsTa29hg=

      Now log into the WireGuard server, and run the following command:

      • sudo wg set wg0 peer PeURxj4Q75RaVhBKkRTpNsBPiPSGb5oQijgJsTa29hg= allowed-ips 10.8.0.2,fd0d:86fa:c3bc::2

      Note that the allowed-ips portion of the command takes a comma separated list of IPv4 and IPv6 addresses. You can specify individual IPs if you would like to restrict the IP address that a peer can assign itself, or a range like in the example if your peers can use any IP address in the VPN range. Also note that no two peers can have the same allowed-ips setting.

      If you would like to update the allowed-ips for an existing peer, you can run the same command again, but change the IP addresses. Multiple IP addresses are supported. For example, to change the WireGuard Peer that you just added to add an IP like 10.8.0.100 to the existing 10.8.0.2 and fd0d:86fa:c3bc::2 IPs, you would run the following:

      • sudo wg set wg0 peer PeURxj4Q75RaVhBKkRTpNsBPiPSGb5oQijgJsTa29hg= allowed-ips 10.8.0.2,10.8.0.100,fd0d:86fa:c3bc::2

      Once you have run the command to add the peer, check the status of the tunnel on the server using the wg command:

      Output

      interface: wg0 public key: U9uE2kb/nrrzsEU58GD3pKFU3TLYDMCbetIsnV8eeFE= private key: (hidden) listening port: 51820 peer: PeURxj4Q75RaVhBKkRTpNsBPiPSGb5oQijgJsTa29hg= allowed ips: 10.8.0.2/32, fd0d:86fa:c3bc::/128

      Notice how the peer line shows the WireGuard Peer’s public key, and the IP addresses, or ranges of addresses that it is allowed to use to assign itself an IP.

      Now that you have defined the peer’s connection parameters on the server, the next step is to start the tunnel on the peer.

      Step 9 — Connecting the WireGuard Peer to the Tunnel

      Now that your server and peer are both configured to support your choice of IPv4, IPv6, packet forwarding, and DNS resolution, it is time to connect the peer to the VPN tunnel.

      Since you may only want the VPN to be on for certain use cases, we’ll use the wg-quick command to establish the connection manually. If you would like to automate starting the tunnel like you did on the server, follow those steps in Step 6 — Starting the WireGuard Server section instead of using the wq-quick command.

      To start the tunnel, run the following on the WireGuard Peer:

      You will receive output like the following:

      Output

      [#] ip link add wg0 type wireguard [#] wg setconf wg0 /dev/fd/63 [#] ip -4 address add 10.8.0.2/24 dev wg0 [#] ip -6 address add fd0d:86fa:c3bc::2/64 dev wg0 [#] ip link set mtu 1420 up dev wg0

      Notice the highlighted IPv4 and IPv6 addresses that you assigned to the peer.

      If you set the AllowedIPs on the peer to 0.0.0.0/0 and ::/0 (or to use ranges other than the ones that you chose for the VPN), then your output will resemble the following:

      Output

      [#] ip link add wg0 type wireguard [#] wg setconf wg0 /dev/fd/63 [#] ip -4 address add 10.8.0.2/24 dev wg0 [#] ip -6 address add fd0d:86fa:c3bc::2/64 dev wg0 [#] ip link set mtu 1420 up dev wg0 [#] mount `67.207.67.2' /etc/resolv.conf [#] wg set wg0 fwmark 51820 [#] ip -6 route add ::/0 dev wg0 table 51820 [#] ip -6 rule add not fwmark 51820 table 51820 [#] ip -6 rule add table main suppress_prefixlength 0 [#] nft -f /dev/fd/63 [#] ip -4 route add 0.0.0.0/0 dev wg0 table 51820 [#] ip -4 rule add not fwmark 51820 table 51820 [#] ip -4 rule add table main suppress_prefixlength 0 [#] sysctl -q net.ipv4.conf.all.src_valid_mark=1 [#] nft -f /dev/fd/63 [#] ip rule add table 200 from 203.0.113.5 [#] ip route add table 200 default via 203.0.113.1

      In this example, notice the highlighted routes that the command added, which correspond to the AllowedIPs in the peer configuration.

      Next, generate some traffic on the tunnel interface by using ping to send a single ICMP packet (indicated by the -c 1 argument in the following commands) to the WireGuard Server:

      If you are routing all traffic over the VPN, you can use one of CloudFlare’s servers instead:

      Now check the status of the tunnel on the peer using the wg command:

      Output

      interface: wg0 public key: PeURxj4Q75RaVhBKkRTpNsBPiPSGb5oQijgJsTa29hg= private key: (hidden) listening port: 49338 fwmark: 0xca6c peer: U9uE2kb/nrrzsEU58GD3pKFU3TLYDMCbetIsnV8eeFE= endpoint: 203.0.113.1:51820 allowed ips: 10.8.0.0/24, fd0d:86fa:c3bc::/64 latest handshake: 1 second ago transfer: 6.50 KiB received, 15.41 KiB sent

      You can also check the status on the server again, and you will receive similar output.

      Verify that your peer is using the VPN by using the ip route and ip -6 route commands. If you are using the VPN as a gateway for all your Internet traffic, check which interface will be used for traffic destined to CloudFlare’s 1.1.1.1 and 2606:4700:4700::1111 DNS resolvers.

      If you are only using WireGuard to access resources on the VPN, substitute a valid IPv4 or IPv6 address like the gateway itself into these commands. For example 10.8.0.1 or fd0d:86fa:c3bc::1.

      Output

      1.1.1.1 dev wg0 table 51820 src 10.8.0.2 uid 1000 cache

      Notice the wg0 device is used and the IPv4 address 10.8.0.2 that you assigned to the peer. Likewise, if you are using IPv6, run the following:

      • ip -6 route get 2606:4700:4700::1111

      Output

      2606:4700:4700::1111 dev wg0 table 51820 src fd0d:86fa:c3bc::2 metric 1024 pref medium

      Again note the wg0 interface, and the IPv6 address fd0d:86fa:c3bc::2 that you assigned to the peer.

      If your peer has a browser installed, you can also visit ipleak.net and ipv6-test.com to confirm that your peer is routing its traffic over the VPN.

      Once you are ready to disconnect from the VPN on the peer, use the wg-quick command:

      You will receive output like the following indicating that the VPN tunnel is shut down:

      Output

      [#] ip link delete dev wg0

      If you set the AllowedIPs on the peer to 0.0.0.0/0 and ::/0 (or to use ranges other than the ones that you chose for the VPN), then your output will resemble the following:

      Output

      [#] ip rule delete table 200 from 137.184.109.48 [#] ip route delete table 200 default via 137.184.96.1 [#] ip -4 rule delete table 51820 [#] ip -4 rule delete table main suppress_prefixlength 0 [#] ip -6 rule delete table 51820 [#] ip -6 rule delete table main suppress_prefixlength 0 [#] ip link delete dev wg0 [#] umount /etc/resolv.conf [#] nft -f /dev/fd/63

      To reconnect to the VPN, run the wg-quick up wg0 command again on the peer. If you would like to completely remove a peer’s configuration from the WireGuard Server, you can run the following command, being sure to substitute the correct public key for the peer that you want to remove:

      • sudo wg set wg0 peer PeURxj4Q75RaVhBKkRTpNsBPiPSGb5oQijgJsTa29hg= remove

      Typically you will only need to remove a peer configuration if the peer no longer exists, or if its encryption keys are compromised or changed. Otherwise it is better to leave the configuration in place so that the peer can reconnect to the VPN without requiring that you add its key and allowed-ips each time.

      Conclusion

      In this tutorial you installed the WireGuard package and tools on both the server and client Rocky Linux 8 systems. You set up firewall rules for WireGuard, and configured kernel settings to allow packet forwarding using the sysctl command on the server. You learned how to generate private and public WireGuard encryption keys, and how to configure the server and peer (or peers) to connect to each other.

      If your network uses IPv6, you also learned how to generate a unique local address range to use with peer connections. Finally, you learned how to limit which traffic should go over the VPN by restricting the network prefixes that the peer can use, as well as how to use the WireGuard Server as a VPN gateway to handle all Internet traffic for peers.

      If you would like to learn more about WireGuard, including how to configure more advanced tunnels, or use WireGuard with containers, visit the official WireGuard documentation.



      Source link