One place for hosting & domains

      Manage

      How To Manage Logfiles with Logrotate on Ubuntu 20.04


      Introduction

      Logrotate is a system utility that manages the automatic rotation and compression of log files. If log files were not rotated, compressed, and periodically pruned, they could eventually consume all available disk space on a system.

      Logrotate is installed by default on Ubuntu 20.04, and is set up to handle the log rotation needs of all installed packages, including rsyslog, the default system log processor.

      In this article, we will explore the default Logrotate configuration, then configure log rotation for a fictional custom application.

      Prerequisites

      This tutorial assumes you have an Ubuntu 20.04 server, with a non-root sudo-enabled user, as described in Initial Server Setup with Ubuntu 20.04.

      Logrotate is available on many other Linux distributions as well, but the default configuration may be quite different. Other sections of this tutorial will still apply as long as your version of Logrotate is similar to Ubuntu 20.04’s. Follow Step 1 to determine your Logrotate version.

      Log into your server as your sudo-enabled user to begin.

      You can also use an interactive terminal that is embedded on this page to experiment with logrotate in this tutorial. Click the following Launch an Interactive Terminal! button to get started.

      Launch an Interactive Terminal!

      Step 1 — Confirming Your Logrotate Version

      Logrotate is installed by default on Ubuntu. However, if you need to install it, run the following commands to update your package list and retrieve the package:

      • sudo apt update
      • sudo apt install logrotate

      If you’re using a non-Ubuntu server, first make sure Logrotate is installed by asking for its version information:

      Output

      logrotate 3.14.0 Default mail command: /usr/bin/mail Default compress command: /bin/gzip Default uncompress command: /bin/gunzip Default compress extension: .gz Default state file path: /var/lib/logrotate/status ACL support: yes SELinux support: yes

      If Logrotate is installed but the version number is significantly different, you may have issues with some of the configuration options that are explored in this tutorial. Refer to the documentation for your specific version of Logrotate by reading its manual (man) page:

      You can also consult the online version of the Logrotate documentation. Next we’ll look at Logrotate’s default configuration structure on Ubuntu.

      Step 2 — Exploring the Logrotate Configuration

      Logrotate’s configuration information can generally be found in two places on Ubuntu:

      • /etc/logrotate.conf: this file contains some default settings and sets up rotation for a few logs that are not owned by any system packages. It also uses an include statement to pull in configuration from any file in the /etc/logrotate.d directory.
      • /etc/logrotate.d/: this is where any packages you install that need help with log rotation will place their Logrotate configuration. On a standard install you should already have files here for core system tools like apt, dpkg, rsyslog and so on.

      By default, logrotate.conf will configure weekly log rotations, with log files owned by the root user and the syslog group, with four log files being retained at a time(rotate 4), and new empty log files being created after the current one is rotated (create).

      Let’s take a look at a package’s Logrotate configuration file in /etc/logrotate.d. cat the file for the apt package utility:

      Output

      /var/log/apt/term.log { rotate 12 monthly compress missingok notifempty } /var/log/apt/history.log { rotate 12 monthly compress missingok notifempty }

      This file contains configuration blocks for two different log files in the /var/log/apt/ directory: term.log and history.log. They both have the same options. Any options not set in these configuration blocks will inherit the default values or those set in /etc/logrotate.conf. Any setting in a logrotate file will override logrotate’s default values, which are configured in /etc/logrotate.conf. The options set for the apt logs are:

      • rotate 12: keep twelve old log files. This overrides the rotate 4 default.
      • monthly: rotate once a month. This overrides the weekly default.
      • compress: compress the rotated files. this uses gzip by default and results in files ending in .gz. The compression command can be changed using the compresscmd option.
      • missingok: don’t write an error message if the log file is missing.
      • notifempty: don’t rotate the log file if it is empty.

      These configuration files also inherit the default create behaviour, which instructs Logrotate to create new logs after rotation. This could be overridden with nocreate, although that would effectively disable most of the other functionality.

      There are many more configuration options available. You can read about all of them by typing man logrotate on the command line to bring up Logrotate’s manual page.

      Next, we’ll set up a configuration file to handle logs for a fictional service called your-app.

      Step 3 — Setting Up an Example Config

      To manage log files using logrotate for applications outside of the pre-packaged and pre-configured system services, we have two options:

      1. Create a new Logrotate configuration file and place it in /etc/logrotate.d/. This will be run daily as the root user along with all the other standard Logrotate jobs.
      2. Create a new configuration file and run it outside of Ubuntu’s default Logrotate setup. This is only really necessary if you need to run Logrotate as a non-root user, or if you want to rotate logs more frequently than daily (an hourly configuration in /etc/logrotate.d/ would be ineffective, because the system’s Logrotate setup only runs once a day).

      Let’s walk through these two options with some example setups.

      Adding Configuration to /etc/logrotate.d/

      We want to configure log rotation for a fictional web server that puts an access.log and error.log into /var/log/your-app/. It runs as the www-data user and group.

      To add a configuration for the your-app log files to /etc/logrotate.d/, first open up a new file in the /etc/logrotate.d directory using nano or your preferred editor:

      • sudo nano /etc/logrotate.d/your-app

      Add the following lines to your new configuration file:

      /etc/logrotate.d/your-app

      /var/log/your-app/*.log {
          daily
          missingok
          rotate 14
          compress
          notifempty
          create 0640 www-data www-data
          sharedscripts
          postrotate
              systemctl reload your-app
          endscript
      }
      

      Some of the new configuration directives in this file are:

      • create 0640 www-data www-data: this creates a new empty log file after rotation, with the specified permissions (0640), owner (www-data), and group (also www-data).
      • sharedscripts: this flag means that any scripts added to the configuration are run only once per run, instead of for each file rotated. Since the path /var/log/your-app/*.log includes a wildcard *, this configuration would match any number of log files in the your-app directory. Without the sharedscripts option, the script specified in postrotate would run each time logrotate processes a log file without this option.
      • postrotate to endscript: this block contains a script to run after the log file is rotated. In this case we’re reloading our example app. This is sometimes necessary to get your application to switch over to the newly created log file.
        Note that postrotate runs before logs are compressed. Compression could take a long time, and your software should switch to the new log file immediately. For tasks that need to run after logs are compressed, use the lastaction block instead.

      To save and quit nano, press Ctrl+X, and when prompted, Y and then Enter. You can test the config file by doing a dry run:

      • sudo logrotate /etc/logrotate.conf --debug

      This command calls logrotate, points it to the standard configuration file, and turns on debug mode.

      Information will print out about which log files Logrotate is handling and what it would have done to them. If all looks well, you’re done. The standard Logrotate job will run once a day and include your new configuration.

      Next, we’ll try a setup that doesn’t use Ubuntu’s default configuration at all.

      Step 4 — Creating an Independent Logrotate Configuration

      In this example we have an app running as our user sammy, generating logs that are stored in /home/sammy/logs/. We want to rotate these logs hourly, so we need to set this up outside of the /etc/logrotate.d structure provided by Ubuntu.

      First, we’ll create a configuration file in our home directory. Open it in a text editor:

      • nano /home/sammy/logrotate.conf

      Then paste in the following configuration:

      /home/sammy/logrotate.conf

      /home/sammy/logs/*.log {
          hourly
          missingok
          rotate 24
          compress
          create
      }
      

      Save and close the file. We’ve encountered all these options in previous steps, but let’s summarize: this configuration will rotate the files hourly, compressing and keeping twenty-four old logs and creating a new log file to replace the rotated one.

      You’ll need to customize the configuration to suit your application, but this is a good start.

      To test that the configuration works, let’s make a log file. First cd to your user’s home directory using the cd ~ command. Then create a directory for the logs using the mkdir command. Finally create an empty file in the logs directory using the touch command. Run the following commands in order to complete these steps:

      • cd ~
      • mkdir logs
      • touch logs/access.log

      Now that we have a blank log file in the right spot, let’s run the logrotate command.

      Because the logs are owned by sammy we don’t need to use sudo. However, we do need to specify a state file. This file records what logrotate found and any actions that it took the last time it ran, so that it knows what to do the next time it runs. This state tracking is handled for us when using the default /etc/logrotate.conf configuration. The state file is stored in /var/lib/logrotate/status. Since we are not using the default configuration, we’ll need to configure the state file location manually.

      We’ll have Logrotate put the state file right in our home directory for this example. It can go anywhere that’s accessible and convenient. Run the following command to use the /home/sammy/logrotate.conf that you created and to record the state that logrotate encounters:

      logrotate /home/sammy/logrotate.conf --state /home/sammy/logrotate-state --verbose
      

      Output

      reading config file /home/sammy/logrotate.conf Handling 1 logs rotating pattern: /home/sammy/logs/*.log hourly (24 rotations) empty log files are rotated, old logs are removed considering log /home/sammy/logs/access.log log does not need rotating

      The --verbose flag will print out detailed information about what Logrotate is doing. In this case it did not rotate anything. This is Logrotate’s first time encountering this log file and it is zero hours old so shouldn’t be rotated.

      If we examine at the state file using the cat utility, we’ll note that Logrotate recorded some information about the run:

      • cat /home/sammy/logrotate-state

      Output

      logrotate state -- version 2 "/home/sammy/logs/access.log" 2021-12-3-19:0:0

      Logrotate noted the logs that it saw and when it last considered them for rotation. If we run this same command one hour later, the log will be rotated as expected.

      If you want to force Logrotate to rotate the log file when it otherwise would not have, use the --force flag:

      • logrotate /home/sammy/logrotate.conf --state /home/sammy/logrotate-state --verbose --force

      This is useful when testing postrotate and other scripts.

      Finally, we need to set up a cron job to run Logrotate every hour. Open your user’s crontab:

      This will open a text file. If it is your first time using cron, you may be prompted to choose a default text editor. If you do not have a preference, we recommend nano for new users. There may be some comments already in the file that explain the cron syntax. Move the cursor down to a new blank line at the end of the file and add the following:

      crontab

      14 * * * * /usr/sbin/logrotate /home/sammy/logrotate.conf --state /home/sammy/logrotate-state

      This task will run on the 14th minute of every hour, every day. It runs nearly the same logrotate command we ran previously, though we expanded logrotate to its full path of /usr/sbin/logrotate to be safe. It’s good practice to use full paths when writing cron jobs. To learn more about cron, you can review our other tutorials.

      Save the file and exit. This will install the crontab and our task will run on the specified schedule.

      If we revisit our log directory in about an hour we should find the rotated and compressed log file access.log.1.gz (or .2.gz if you ran Logrotate with the --force flag).

      Conclusion

      In this tutorial we verified our Logrotate version, explored the default Ubuntu Logrotate configuration, and set up two different types of custom configurations. To learn more about the command line and configuration options available for Logrotate, you can read its manual page by running man logrotate in your terminal or by visiting the online documentation.



      Source link

      How To Securely Manage Secrets with HashiCorp Vault on Ubuntu 20.04


      Introduction

      Vault is an open-source tool that provides a secure, reliable way to store and distribute secrets like API keys, access tokens, and passwords. Software like Vault can be critically important when deploying applications that require the use of secrets or sensitive data.

      In this tutorial, you will:

      • Install Vault and configure it as a system service
      • Initialize an encrypted on-disk data store
      • Store and retrieve a sensitive value securely over TLS

      With some additional policies in place, you’ll be able to use Vault to securely manage sensitive data for your various applications and tools.

      As with any service that manages sensitive information, you should consider reading additional documentation regarding Vault’s deployment best practices before using it in a production-like environment. For example, Vault’s production hardening guide covers topics such as policies, root tokens, and auditing.

      Prerequisites

      Before you begin this guide you’ll need the following:

      Note: Vault generates a self-signed TLS certificate when you install the package for the first time. If you do not have a domain name or TLS certificate to use with Vault but would like to follow the steps in this tutorial, you can skip TLS verification by adding the -tls-skip-verify flag to the commands in this tutorial, or by defining the VAULT_SKIP_VERIFY environment variable.

      This option is only suitable for experimenting with Vault and should not be used in a production environment.

      Step 1 — Installing Vault

      HashiCorp provides Vault as a typical Debian/Ubuntu package, so we’ll go through the normal steps of adding their package repository to our server’s list of package sources:

      First, add Hashicorp’s GPG key to your package manager, so that your system trusts their package repositories:

      • curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -

      Then add the repository itself to your list of package sources, so it’ll be checked for regular updates:

      • sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"

      Then install the package:

      You can now use the vault command. Try checking Vault’s version to make sure it works.

      Output

      Vault v1.8.5 (647eccfe0bd5817bdd8628f3c3171402dfc8a8fc)

      The Vault executable is installed on your server, so the next step is to configure it to run as a system service.

      Step 2 — Configuring Vault

      Installing the Vault package automatically creates a vaultvault user on your system and sets up a system service for running Vault in the background. We need to make a couple of changes to its default configuration in order to use the HTTPS certificates generated by Let’s Encrypt.

      Note: In this tutorial and by default, Vault uses the filesystem backend to store encrypted secrets on the local filesystem at /opt/vault. This is suitable for local or single-server deployments that do not need to be replicated. Other Vault backends, such as the Consul backend, will store encrypted secrets at rest within a distributed key/value store.

      Vault’s default configuration is stored in /etc/vault.d/vault.hcl. You’ll use this file to control various options in Vault, such as where encrypted secrets are stored.

      Open vault.hcl using nano or your favorite text editor.

      • sudo nano /etc/vault.d/vault.hcl

      Find the listener "tcp" section of the file that contains this block. If you are using nano, you can press Ctrl+W then enter listener “tcp” to find that line directly:

      /etc/vault.hcl

      listener "tcp" {
        address       = "0.0.0.0:8200"
        tls_cert_file = "/opt/vault/tls/tls.crt"
        tls_key_file  = "/opt/vault/tls/tls.key"
      ...
      }
      

      Edit the tls_cert_file and tls_key_file lines to point to your Let’s Encrypt certificate and key files. Don’t forget to substitute in your own domain name in place of the highlighted your_domain part of each line.

      /etc/vault.hcl

      listener "tcp" {
      ...
        tls_cert_file = "/etc/letsencrypt/live/your_domain/fullchain.pem"
        tls_key_file = "/etc/letsencrypt/live/your_domain/privkey.pem"
      }
      

      Note: You should also change address = "0.0.0.0:8200" to address = “127.0.0.1:8200” to prevent external connections to this server for now. 127.0.0.1 is a reserved address for localhost only. This is to ensure that the service is not exposed to the public internet before it has been properly secured. You can update this later, but for now, this configuration change will let us use the vault command and correctly resolve the HTTPS-secured domain name.

      Save and close the file. If you are using nano, press Ctrl+X, then Y when prompted to save the file, and Enter to confirm.

      Next, the vault system user also needs permission to read these certificates. By default, these certificates and private keys are only accessible by root. To make these available securely, we’ll create a special group called pki to access these files. We will create the group and then add the vault user to it.

      Update the permissions on the two directories in the /etc/letsencrypt directory to allow members of the pki group to read the contents.

      • sudo chgrp pki /etc/letsencrypt/archive
      • sudo chgrp pki /etc/letsencrypt/live
      • sudo chmod g+rx /etc/letsencrypt/archive
      • sudo chmod g+rx /etc/letsencrypt/live

      Then add the vault user to the pki group. This will grant Vault access to the certificates so that it can serve requests securely over HTTPS.

      • sudo usermod -a -G pki vault

      As a final step for convenience, add a rule in /etc/hosts to direct requests to Vault to localhost.

      Replace your_domain in the following command with the domain you acquired the Let’s Encrypt certificate for:

      • echo 127.0.0.1 your_domain.com | sudo tee -a /etc/hosts

      This command appends the line 127.0.0.1 your_domain.com to /etc/hosts so that any HTTP requests that you make on your Vault server to your_domain.com ignore DNS and are sent to localhost directly.

      With the Vault service set up and the Vault configuration file complete, we’re now ready to start Vault and initialize the secret store.

      Step 3 — Initializing Vault

      When you first start Vault, it will be uninitialized, which means that it isn’t ready to receive and store data. In this section of the tutorial, you will start the Vault server, and then initialize it with a set of secret keys that will be used to unseal (open) Vault’s secret stores.

      The first time you start Vault, the backend that actually stores the encrypted secrets is uninitialized, too. Start the Vault system service to initialize the backend and start running Vault itself.

      • sudo systemctl start vault.service

      You can run a quick check to confirm the service has started successfully.

      • sudo systemctl status vault.service

      You should receive output similar to the following:

      Output

      ● vault.service - "HashiCorp Vault - A tool for managing secrets" Loaded: loaded (/lib/systemd/system/vault.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2021-11-16 21:55:13 UTC; 22s ago Docs: https://www.vaultproject.io/docs/ Main PID: 104207 (vault) Tasks: 7 (limit: 1137) Memory: 179.3M CGroup: /system.slice/vault.service └─104207 /usr/bin/vault server -config=/etc/vault.d/vault.hcl

      The output of that command should include several pieces of information about the running service, such as its process ID and resource usage. Ensure that the following line is included in the output, which indicates that the service is running correctly.

      Output

      . . . Active: active (running) . . .

      If the service is not active, take a look at the accompanying log lines at the end of the command’s output to see Vault’s output, which can help pinpoint any issues.

      Next, we’ll set an environment variable to tell the vault command how to connect to the Vault server. In Step 1 you configured Vault to only listen on the local loopback interface, so set the VAULT_ADDR environment variable to the local HTTPS endpoint.

      • export VAULT_ADDR=https://your_domain:8200

      The vault command can now communicate with the daemon. Note that defining the actual hostname instead of simply localhost or 127.0.0.1 is necessary to properly validate the HTTPS certificate.

      Confirm that the vault server is in an uninitialized state by checking its status.

      The server should return some output so that you can tell it is working but not yet initialized.

      Key Value
      --- -----
      Seal Type shamir
      Initialized false
      Sealed true
      Total Shares 0
      Threshold 0
      Unseal Progress 0/0
      Unseal Nonce n/a
      Version 1.8.5
      Storage Type file
      HA Enabled false
      

      There are two pieces of information that Vault will expose at initialization time that will not be available at any other point:

      • Initial root token. This is equivalent to root permissions to your Vault deployment, which allows the management of all Vault policies, mounts, and secrets.
      • Unseal keys. These are used to unseal Vault when the daemon starts, which permits the Vault daemon to decrypt the backend secret store.

      More specifically, Vault’s unsealing process decrypts the backend using a key formed by key shares. When you first initialize Vault, you may choose how many unseal keys to create and how many are necessary at unseal time to successfully unseal Vault. To learn more about Vault’s sealing mechanism, you can refer to the Vault documentation.

      A typical configuration for the unseal parameters would be to create three keys and require at least two of those keys at unseal time. This permits the important key shares to be separated and stored in distinct locations to ensure that compromising one is not sufficient to unseal Vault.

      In other words, whenever Vault is started, at least two unseal keys will be required in order to make the service become available and ready to use. While sealed, the files that store the actual secret values will remain encrypted and inaccessible.

      Initialize Vault with three unseal keys using the -key-shares=3 option, and require at least two keys to unseal the vault with the -key-threshold=2 flag::

      • vault operator init -key-shares=3 -key-threshold=2

      You will receive output like the following:

      Output

      Unseal Key 1: eZcJeydRrqeSMZ1zTN+VVll9TFT2KvJy7VlnxUgtvuz5 Unseal Key 2: ntmqCKq8rgNnKT1YSLCjVmCCZBAA3NwUeqxIyRpYD4Wm Unseal Key 3: 3FK1+Hsorh4p8/L9mki3VskaEU2eQhLqGOI/pJkTHMbx Initial Root Token: s.hY0ieybfDqCadz7JpL88uO3x

      Be sure to save each unseal token and the initial root token in a secure way. You will not be able to retrieve these keys and root token again. For example, one option would be to store one unseal key in a password manager, another on a USB drive, and another in a GPG-encrypted file.

      If you examine vault status again, the Initialized status will now be set to true, and the Total Shares and Threshold values will reflect the number of key shards and minimum number of keys that you will need to unseal the vault.

      Output

      . . . Initialized true Sealed true Total Shares 3 Threshold 2 Unseal Progress 0/2 . . .

      Note that the Unseal Progess line shows the value 0/2. Begin unsealing Vault using your newly created unseal tokens. Run the vault operator unseal command and input any of your keys when prompted:

      The command will ask for an unseal token:

      Output

      Key (will be hidden):

      After entering it, the output from the command will indicate that the unsealing is in progress, but still requires one more unsealing key before Vault is ready for use.

      Output

      Key Value --- ----- Seal Type shamir Initialized true Sealed true Total Shares 3 Threshold 2 Unseal Progress 1/2 Unseal Nonce 0f3a328b-e0c6-6294-d6a2-56da49271dff Version 1.8.5 Storage Type file HA Enabled false

      Notice how the Unseal Progress 1/2 line has changed in the output. Run the unseal command again.

      And enter a different key than the one you already used:

      Output

      Key (will be hidden):

      The command’s output indicates that the unseal process had completed successfully.

      Output

      Key Value --- ----- Seal Type shamir Initialized true Sealed false Total Shares 3 Threshold 2 Version 1.8.5 Storage Type file Cluster Name vault-cluster-3042c7bc Cluster ID c3e9d814-cf2a-2901-f0e4-ebc52d29e5cc HA Enabled false

      Vault is now unsealed and ready for use. These unseal steps are necessary whenever Vault is started or restarted.

      However, unsealing is a distinct process from normal interaction with Vault (such as reading and writing values), which are authenticated by tokens. In the next steps, we’ll create the necessary access tokens and policies to store secret values and read/write to specific paths in Vault.

      Step 4 — Reading and Writing Secrets

      There are several [secret backends]](https://www.vaultproject.io/docs/secrets/index.html) that you can use with Vault, but for this example we will use the kv secret backend. This backend stores simple key/value pairs in Vault. However, it is not enabled by default.

      In this section of the tutorial, you will enable the kv secret backend, and then learn how to read and write secrets to it.

      First, save the previously generated root token to a shell variable for ease of use.

      • root_token=your_root_token_here

      Next, while authenticating with the root token, enable the kv backend:

      • VAULT_TOKEN=$root_token vault secrets enable kv

      You will receive output like the following if the command is successful:

      Output

      Success! Enabled the kv secrets engine at: kv/

      You can then verify that it’s been added to your local list of available secrets backends:

      • VAULT_TOKEN=$root_token vault secrets list

      You should receive output like the following:

      Output

      Path Type Accessor Description ---- ---- -------- ----------- cubbyhole/ cubbyhole cubbyhole_abc1631b per-token private secret storage identity/ identity identity_631fe262 identity store kv/ kv kv_4d5855c8 n/a sys/ system system_79b13f2f system endpoints used for control, policy and debugging

      Note the highlighted line that indicates the new kv backend is enabled. Now we can store some data with this backend.

      • VAULT_TOKEN=$root_token vault write kv/message value=mypassword

      In this command, the highlighted kv/ prefix indicates that we are writing to the kv backend mounted at the kv path, and we are storing the key value at the path message with the value mypassword. We used the root token, which has superuser privileges, to write the generic secret.

      Check the secret that you created using the vault read command:

      • VAULT_TOKEN=$root_token vault read kv/message

      You should receive output like the following, with the contents of the secret that you created:

      Output

      Key Value --- ----- refresh_interval 768h value mypassword

      However, creating, reading, and otherwise interacting with Vault using the Root Token only is not secure, scalable in a team setting, and does not allow for fine-grained access control to secrets. In the next section you’ll learn how to define policies and create additional access tokens to restrict how users can interact with Vault.

      Step 5 — Creating an Authorization Policy

      In a real-world scenario, you may store values like API keys or passwords that external tools can consume. Although you may read the secret value again using the root token, it is illustrative to generate a less privileged token with read-only permissions to our single secret.

      In this section of the tutorial you will create a Vault policy that will enforce read-only access to secrets. To create the policy you’ll need to edit a file and then load it into Vault using the vault policy command.

      To get started creating a policy, open a file called policy.hcl using nano or your preferred editor:

      Populate the file with the following Vault policy, which defines read-only access to the secret path::

      policy.hcl

      path "kv/message" {
           capabilities = ["read"]
      }
      

      Save and close the file, then write this policy to Vault. The following command will load the policy.hcl file into Vault and create a policy named message-readonly with the read-only capability

      • VAULT_TOKEN=$root_token vault policy write message-readonly policy.hcl

      Next, create a token that you will use for read-only access to Vault. Add the -policy=”message-readonly” flag to the vault token create command to use the new policy that you created:

      • VAULT_TOKEN=$root_token vault token create -policy="message-readonly"

      The output will look like this:

      Output

      Key Value --- ----- token your_token_value token_accessor your_token_accessor token_duration 768h0m0s token_renewable true token_policies ["default" "message-readonly"] identity_policies [] policies ["default" "message-readonly"]

      Save the highlighted your_token_value value to an environment variable called app_token:

      • app_token=your_token_value

      You can use the value of app_token to access the data stored in the path kv/message (and no other values in Vault).

      • VAULT_TOKEN=$app_token vault read kv/message

      Output

      Key Value --- ----- refresh_interval 768h0m0s value mypassword

      You can also test that this unprivileged token cannot perform other operations, such as listing secrets in Vault.

      • VAULT_TOKEN=$app_token vault list kv/

      Output

      Error reading kv/: Error making API request. URL: GET https://your_domain:8200/v1/secret?list=true Code: 403. Errors: * 1 error occurred: * permission denied

      This verifies that the less-privileged app token cannot perform any destructive actions or access other secret values aside from those explicitly stated in its Vault policy. If you would like to continue using the read-only token, be sure to record it somewhere safe for future use.

      Conclusion

      In this article you installed, configured, and deployed Vault on Ubuntu 20.04. You also created a sharded key to unseal Vault, enabled the kv backend secret store, and defined a read-only policy to limit how a user can interact with Vault secrets.

      Although this tutorial only demonstrated how to use an unprivileged token, the Vault documentation has more examples of additional ways to store and access secrets as well as alternative authentication methods.

      These instructions demonstrated how to deploy and use some of the core features of Vault. Your needs may require other configuration changes and more complex policies. Be sure to read the Vault documentation and make appropriate configuration changes for your needs. Some production-ready changes may include:

      • Generating lesser-privileged tokens for everyday use. The specific policies that these tokens should use depends on your specific use cases, but the example app_token in this tutorial illustrates how you can create limited-privilege tokens and policies.

      • If you are deploying Vault as part of a service that will be used by a team, initialize Vault with unseal keys for each team member. This approach can ensure that Vault’s storage is only decrypted when more than one team member participates in the process.



      Source link

      How To Manage State in a Vue.js Application with Vuex


      The author selected Open Sourcing Mental Illness to receive a donation as part of the Write for DOnations program.

      Introduction

      Vuex is the first-party development state management library for Vue.js. It was created by Evan You and is currently maintained by the Vue.js Core Team. Like many other state management libraries, Vuex follows the principle that Redux has popularized over the past years: Data flows in one direction, with actions and mutations modifying data in a single source of truth called the store.

      A Vuex store is a collection of different methods and data. Some of these methods, such as actions, can fetch and process data before it is sent to a mutation. A mutation is a method that mutates or updates the store property with the value provided. Getters are methods that can modify or combine data to create a new state property. These getters are read-only and do not mutate data. These are similar to computed properties in a Vue.js component. The last component to Vuex is the state, or the dataset that acts as your single source of truth.

      In this tutorial, you will create an application that renders a list of cards with airport information in them. When clicked, these cards will execute the Vuex workflow to add the selected airport to a list of favorites. By running through this example, you will make actions and mutations to manage state and getters to retrieve computed data.

      Prerequisites

      Step 1 — Setting Up the Example Application

      In order to help visualize how state is managed with Vuex, set up a project with some data to display in the view. You will use this project and throughout the tutorial.

      Once the favorite-airports project is created as described in the Prerequisites section, create a directory to hold all of your local data for this project. Open your terminal and run the following commands in the project root directory (favorite-airports):

      • mkdir src/data
      • touch src/data/airports.js

      This will create the data directory and an empty airports.js file inside it.

      In your text editor of choice, open the newly created airports.js file and add in the following:

      favorite-airports/src/data/airports.js

      export default [
        {
          name: 'Cincinnati/Northern Kentucky International Airport',
          abbreviation: 'CVG',
          city: 'Hebron',
          state: 'KY'
        },
        {
          name: 'Seattle-Tacoma International Airport',
          abbreviation: 'SEA',
          city: 'Seattle',
          state: 'WA',
        },
        {
          name: 'Minneapolis-Saint Paul International Airport',
          abbreviation: 'MSP',
          city: 'Bloomington',
          state: 'MN',
        },
        {
          name: 'Louis Armstrong New Orleans International Airport',
          abbreviation: 'MSY',
          city: 'New Orleans',
          state: 'LA',
        },
        {
          name: `Chicago O'hare International Airport`,
          abbreviation: 'ORD',
          city: 'Chicago',
          state: 'IL',
        },
        {
          name: `Miami International Airport`,
          abbreviation: 'MIA',
          city: 'Miami',
          state: 'FL',
        }
      ]
      

      This is an array of objects consisting of a few airports in the United States. In this application, you are going to iterate through this data to generate cards consisting of the name, abbreviation, city, and state properties. When the user clicks on a card, you will execute a dispatch method, which will add that airport to your Vuex state as a favorite airport.

      Save data/airports.js and return to the terminal.

      When you’ve completed that step, create a single-file component (SFC) with the name AirportCard.vue. This file will live in the components directory of your project. This component will contain all the styles and logic for the airport card. In your terminal, create the .vue file using the touch command:

      • touch src/components/AirportCard.vue

      Open AirportCard.vue in your text editor and add the following:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}</p>
        </div>
      </template>
      
      <script>
      export default {
        props: {
          airport: {
            type: Object,
            required: true
          }
        }
      }
      </script>
      
      <style scoped>
      .airport {
        border: 3px solid;
        border-radius: .5rem;
        padding: 1rem;
        margin-bottom: 1rem;
      }
      
      .airport p:first-child {
        font-weight: bold;
        font-size: 2.5rem;
        margin: 1rem 0;
      }
      
      .airport p:last-child {
        font-style: italic;
        font-size: .8rem;
      }
      </style>
      

      You may notice that there is some CSS included in this code snippet. In the AirportCard.vue component, the wrapper <div> contains the class of airport. This CSS adds some styling to the generated HTML by adding borders to give each airport the appearance of a “card”. The :first-child and :last-child are pseudo selectors that apply different styling to the first and last p tags in the HTML inside the div with the class of airport. In addition to that, you may also notice that this component contains a prop, which in Vue.js is a way to pass data down from a parent component to a child component.

      Save and exit from the file.

      Before wrapping up the setup, replace the existing App.vue component with the following code:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
          </div>
        </div>
      </template>
      
      <script>
      import { ref } from 'vue'
      import allAirports from '@/data/airports.js'
      import AirportCard from '@/components/AirportCard.vue'
      
      export default {
        components: {
          AirportCard
        },
        setup() {
          const airports = ref(allAirports)
          return { airports }
        }
      }
      </script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      
      .wrapper {
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        grid-column-gap: 1rem;
        max-width: 960px;
        margin: 0 auto;
      }
      
      p,
      h3 {
        grid-column: span 3;
      }
      </style>
      

      This code contains a v-for loop that iterates through the airports.js data and renders a series of AirportCards.vue components with airport data passed in via the prop :airport. Save this code and return to the command line.

      With the project set up, run the local development server using the npm run serve command in your terminal:

      This will start a server on your localhost, usually at port 8080. Open your web browser of choice and visit localhost:8080to see the following:

      A list of cards rendered in Vue using the v-for directive.

      Now that your example application is set up, in the next step you are going to install the Vuex library and create a store. This store is a collection of a number of different Vuex items including: state, mutations, actions, and getters. To illustrate this, you will be executing dispatch methods, which will add an airport to a favorites section of your app.

      Step 2 — Installing Vuex

      When working on web-based applications, you will often work with state. State is a collection of data at a given time. This state can be changed with user interactions via dispatch and commit methods. When the user modifies data, a dispatch event is executed, which passes data to a mutation and updates the state object.

      There are a few ways to approach updating state. Some developers skip actions and go straight to mutatations. However, for the sake of this tutorial, you will always execute an action that in turn calls a mutation. This way you can have multiple mutations inside of an action. The cardinal rule of Vuex is that mutations have one job and one job only: update the store. Actions can do a number of different things including combining data, fetching data, and running JavaScript logic.

      In addition to actions, there are also getters. A getter is a way to combine multiple state values into a single value. If you are familiar with computed properties in Vue.js, getters can be thought of as state-specific computed properties.

      With the Vuex terminology covered, start installing and integrating Vuex. Open your terminal and run the following command:

      • npm install vuex@next --save

      This command will install the version of Vuex that is the most compatible with Vue.js 3.x and saves it in your package.json file. Next, create a directory and an index file for your store. You will use the mkdir command to make a directory and touch to create a new file:

      • mkdir src/store
      • touch src/store/index.js

      Open your text editor and in your store/index.js file, initialize your Vuex store. To do this, you need to leverage the createStore function from Vuex:

      airport-favorites/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
      
      })
      

      You also export this, as you will later import it into your main.js file.

      At this point, you have your Vuex store set up, but the application is not yet aware of it or how to use it. To fully initialize the store, import it into your main.js file. In your text editor, open the src/main.js file.

      Immediately after createApp(App), chain the use method and pass into the store that you are importing, as shown in the following highlighted code:

      favorite-airports/src/main.js

      import { createApp } from 'vue'
      import App from './App.vue'
      import store from './store'
      
      createApp(App).use(store).mount('#app')
      

      Once you’ve chained the use method, save this file. The use method tells the Vue application which code to bundle together when the application is built. In this case, you are telling Vue to “use” or bundle the Vuex store.

      Before you move on to the next section, add a state value into your store and reference it in the App.vue file. Open your store/index.js file and add the following objects and values:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
      state: {
          firstName: 'John',
          lastName: 'Doe'
        },
      mutations: {
      
      },
      actions: {
      
      },
      getters: {
      
      }
      })
      

      These properties reflect the type of data the store holds: state for state (global data), mutations (commits that mutate data), actions (dispatches that call mutations), and getters (store computed properties).

      Save store/index.js, then open your App.vue file in your text editor and add the following:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <p>{{ $store.state.firstName }} {{ $store.state.lastName }}</p>
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
          </div>
        </div>
      </template>
      ...
      

      The $store in this case is the global store that you initialized in the main.js file. If you were to log this.$store into the console, you would see the store object. From there, the code accesses the property you want to display via dot notation.

      Save App.vue then open your web browser. Above the airport cards, you will see the first and last name that you saved to your Vuex store. These are the default values of firstName and lastName, respectively.

      Data from the Vuex store displayed in the view.

      In this step, you installed Vuex and created a Vuex store. You added some default store data and displayed it in the view with the $store object using dot notion. In the next step, you will be updating your Vuex store via actions and mutations, and you will get combined data with getters.

      Step 3 — Creating Actions, Mutations, and Getters

      In Step 2, you installed Vuex manually and integrated it into your project. In this step you still have the first and last name rendered in your browser, but you will create a Vuex getter to render the data as one string. As mentioned before, you can think of Vuex getters as computed properties for your Vuex store.

      To create a getter, open your src/store/index.js file in your text editor of choice. Once open, create a property in the getters object with a function as its value. The name of the property is how you will access the getter later.

      Add the following highlighted code:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
          firstName: 'John',
          lastName: 'Doe'
        },
        ...
        getters: {
          fullName: function () {
      
          }
        }
      })
      

      In this case, you will use the function to combine the first and last names and store the resulting property as fullName. Inside the function, you will need to pass in the state object that is inside your Vuex store. From there, return a string with the first and last name interpolated:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
          firstName: 'John',
          lastName: 'Doe'
        },
        ...
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
        }
      })
      

      You are using template literals here to put the firstName and lastName into one string.

      Save this file, then move back to App.vue. In this file, remove the first and last values and replace them with the getter:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <p>{{ $store.getters.fullName }}</p>
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
          </div>
        </div>
      </template>
      ...
      

      Once you make this change and save the file, your browser will hot reload. You will see the first and last name in your browser as before, but now you are leveraging the getter. If you change one of the names in your Vuex store, the getter will be updated automatically.

      Moving on from getters, there are actions. As mentioned in the last step, for the sake of this tutorial, you will always use an action rather than mutating the data directly.

      In this project, you will add an airport’s data to a “favorites” list when a user clicks on the card. You are going to first get the action and mutation created, then later assign it to a click event using the v-on directive.

      To create an action, open the src/store/index.js file in your text editor. In the actions section of the store, create a function. Like the getter, the function name will be how you reference the action later. Name this function addToFavorites:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
          firstName: 'John',
          lastName: 'Doe',
          favorites: [] // will store favorites here
        },
        mutations: {
      
        },
        actions: {
          addToFavorites() {
      
          }
        },
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
      }
      })
      

      An action accepts two arguments: the context, or the Vue app itself, and the payload, or the data that you want to add to the store. The context has a commit method associated with it that you will use to call a mutation you will make later:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
          firstName: 'John',
          lastName: 'Doe',
          favorites: []
        },
        mutations: {
      
        },
        actions: {
          addToFavorites(context, payload) {
            context.commit('UPDATE_FAVORITES', payload)
          }
        },
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
       }
      })
      

      The commit method also accepts two arguments: the name of the mutation to call and the payload or the data that the mutation will replace the state with.

      In this code, you are naming the mutation UPDATE_FAVORITES. Mutation names should be agnostic and not named after a specific action. For example, a mutation like ADD_FAVORITE and REMOVE_FAVORITE implies a logic, like removing or adding a piece of data. This is not ideal, since mutations should have one job and one job only: update the state. To differentiate between adding and removing data, you could have two different actions that remove or add a favorite airport from the array, which then execute a single mutation called UPDATE_FAVORITES that updates the array with whatever was passed in. Minimizing the amount of mutations you have in your store will help make your Vuex store easier to manage as it grows larger in complexity and size.

      Next, add some logic to this action. When you add an airport as a “favorite”, you will add that payload (the airport data) to the existing array. To do that, you can use the push method in JavaScript:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
          firstName: 'John',
          lastName: 'Doe',
          favorites: []
        },
        mutations: {
      
        },
        actions: {
          addToFavorites(context, payload) {
            const favorites = context.state.favorites
            favorites.push(payload)
            context.commit('UPDATE_FAVORITES', favorites)
          }
        },
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
       }
      })
      

      At this point, your action is set up to to add the payload to your favorites array then call a mutation with the mutated array as the new data. Next, you will define the UPDATE_FAVORITES mutation. Add the following code to set the favorites array as a new array:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
        state: {
            firstName: 'John',
          lastName: 'Doe',
          favorites: []
          },
        mutations: {
          UPDATE_FAVORITES(state, payload) {
            state.favorites = payload
          }
        },
        actions: {
          addToFavorites(context, payload) {
            const favorites = context.state.favorites
            favorites.push(payload)
            context.commit('UPDATE_FAVORITES', favorites)
          }
        },
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
       }
      })
      

      Now that you have your action and your mutation, you can save this file.

      To execute this action, you can call a dispatch event when the user clicks on a card. You will do this with the v-on directive.

      Open the App.vue file in your text editor. On the <airport-card /> component, add the v-on directive shorthand syntax (@) with the event being click:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <p>{{ $store.getters.fullName }}</p>
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" @click="$store.dispatch('addToFavorites', airport)" />
          </div>
          <h2 v-if="$store.state.favorites.length">Favorites</h2>
          <div v-for="airport in $store.state.favorites" :key="airport.abbreviation">
            <airport-card :airport="airport"  />
          </div>
        </div>
      </template>
      ...
      

      The dispatch function accepts two arguments: the action name and the payload data that you are sending to the action.

      Save this file and open it in your browser. Now, when you click on an airport card, the action will call the mutation that updates the state and adds the airport to the favorites property.

      A favorite airport added to the favorite airports section after a Vuex mutation was executed.

      In this step, you expanded on the Vuex store that you created earlier. You created an action that copies an array and pushes a new item to that array. That action called a mutation that in turn updated the state. In addition to that, you learned about getters and how they can be leveraged to create new properties by combining or modifying read-only values in the Vuex store.

      In the final step, you will implement Vuex modules. Modules are a great way to break up your Vuex store into smaller Vuex stores. This is useful as your Vuex store growers larger in size and complexity.

      Step 4 — Composing Vuex Modules

      Modules are smaller Vuex stores that are combined into a single Vuex store. This is similar to how multiple Vue.js components are imported into a single .vue file, such as App.vue. In this step, you are going to separate this Vuex store into two separate modules. One module will be for a user state, and the other will be specific to airport state, actions, and mutations.

      In your terminal, cd into the store directory and use the touch command to create two separate files.

      • touch src/store/user.module.js
      • touch src/store/airports.module.js

      Inside of the user.module.js file, create an object that will be exported by default by adding the following code:

      favorite-airports/src/store/user.module.js

      export default {
        namespaced: true
      }
      

      You are also adding the property namespaced with its value as true. The namespace property will make it so you can reference the module name while accessing a property with dot notation later.

      Inside of this object, you will add the state and getter information that is associated with a user:

      favorite-airports/src/store/user.module.js

      export default {
        namespaced: true,
        state: {
          firstName: 'John',
          lastName: 'Doe'
        },
        getters: {
          fullName: function (state) {
            return `${state.firstName} ${state.lastName}`
          }
        }
      }
      

      The user module contains everything that you need for user information. Save and exit from the file.

      Go ahead and do the same thing in the airports.module.js file. Open the airports.module.js file in your text editor and add the following:

      favorite-airports/src/store/airports.module.js

      export default {
        state: {
          favorites: []
        },
        mutations: {
          UPDATE_FAVORITES(state, payload) {
            state.favorites = payload
          }
        },
        actions: {
          addToFavorites(context, payload) {
            const favorites = context.state.favorites
            favorites.push(payload)
            context.commit('UPDATE_FAVORITES', favorites)
          }
        },
      }
      

      Now that you have put the airport-related mutations, actions, and state, you can save your airports.module.js.

      Next, import these two files into the main store/index.js file. In your text editor, open the store/index.js file and remove the state, mutations, actions, and getters properties. Your file will resemble the following snippet:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      
      export default createStore({
      
      })
      

      To register modules, you will need to import them into this index.js file with the following highlighted code:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      import UserModule from './user.module.js'
      import AirportsModule from './airports.module.js'
      
      export default createStore({
      
      })
      

      From here, you will need to have a property called modules with an object as its value. The property names inside of this object will be the names of the Vuex modules. The value of the module name is the imported module itself:

      favorite-airports/src/store/index.js

      import { createStore } from 'vuex'
      import UserModule from './user.module.js'
      import AirportsModule from './airports.module.js'
      
      export default createStore({
      modules: {
        user: UserModule,
        airports: AirportsModule
      }
      })
      

      Once you save this file, your modules have now been registered and combined into your single Vuex store. Save store/index.js, then open the App.vue file and update it to reference the newly created modules:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <p>{{ $store.getters['user/fullName'] }}</p>
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" @click="$store.dispatch('addToFavorites', airport)" />
          </div>
          <h2 v-if="$store.state.airports.favorites.length">Favorites</h2>
          <div v-for="airport in $store.state.airports.favorites" :key="airport.abbreviation">
            <airport-card :airport="airport"  />
          </div>
        </div>
      </template>
      ...
      

      Now you have a modular version of your Vuex setup.

      In this step, you segmented your existing Vuex store into smaller chunks called modules. These modules are a great way to group related store properties into a smaller Vuex store. You also updated your App.vue to reference the state and dispatch events in each module.

      Conclusion

      At a high level, state management is all about updating data. In this setup, the data in the state is global throughout your entire application and acts as a single source of truth, which can only be updated with explicit functions in the form of actions and mutations. In this tutorial, you ran through examples of state, mutations, actions, and getters and saw how each of these properties have their own purpose in the update cycle.

      To learn more about Vuex, actions, mutations, and modules, review the official Vuex documentation written by the Vue.js Core Team. For more tutorials on Vue, check out the How To Develop Websites with Vue.js series page.



      Source link