Sunday, May 16, 2021

Ansible - create user with no password but can only login with su

This user "cherryshoe_admin" is only used after I log into the system as myself first with "cherryshoe", and then switch to this user to perform certain tasks such as running application services.  Because the "cherryshoe_admin" user is meant to have no password, the ansible user module creates the user "cherryshoe_admin" with no login shell. 

Environment:

control node OS - CentOS Linux release 7.3.1611 (Core)
control node ansible - 2.9.18
managed nodes OS - RHEL 7 (with SELinux as Enforcing)

Original ansible tasks:

- name: set up project group cherryshoe_admin
  group:
    name: "cherryshoe_admin"
    state: present

- name: set up project user cherryshoe_admin.
  user: name='cherryshoe_admin' group='cherryshoe_admin'

This exclamation points after the username show that the user is locked and "cherryshoe" is unable to su login to "cherryshoe_admin":

$ sudo cat /etc/shadow | grep cherryshoe_admin
cherryshoe_admin:!!:18739:0:99999:7:::

To fix the problem, added additional new task to explicitly force no password for user "cherryshoe_admin":

# cherryshoe_admin needs to have a login shell for su purposes, so unlock account
- block:
    - name: unlock cherryshoe_admin user account for su purposes with no password
      shell: passwd -u cherryshoe_admin -f

This shows that the user is not locked:

$ sudo cat /etc/shadow | grep cherryshoe_admin
cherryshoe_admin::18739:0:99999:7:::

As my user "cherryshoe", am now able to "su cherryshoe_admin" without a password.


P.S.  I also tried using the ansible user module to create the user with a default shell and empty password, but it creates the user as locked:

- name: set up project user cherryshoe_admin.
  user: name='cherryshoe_admin' group='cherryshoe_admin' shell=/bin/bash password=""

Sunday, April 18, 2021

Redirect users of Internet Explorer with Nginx

The project I'm on recently decided to stop Internet Explorer support.  Below is how to redirect users of Internet Explorer to a different webpage using the Nginx web server.  It allowed removing IE support configured via the React package.json browserlist attribute and removing any IE-specific polyfill code that was used.

Environment
CentOS Linux release 7.3.1611 (Core) 
React 17.1
nginx version: nginx/1.16.1

Use this outside of a server or location configuration.  Trident is to detect modern IE in compatibility mode.  The Nginx map module creates the variable $outdated based on values from the $http_user_agent variable.

map $http_user_agent $outdated {
    default              0;
    "~MSIE [1-11]\."     1;
    "~Trident/[1-7]\."    1;
}

To test it, can add a custom header inside the server or location configuration.

add_header X-debug-http_user_agent "http_user_agent $http_user_agent" always;
add_header X-debug-outdated "outdated $outdated" always;

After testing that the http_user_agent and outdated have the values you expect, then add in the redirect inside a server or location configuration.  I chose HTTP Status 307 because it's a temporary redirect and because it guarantees that the method and the body will not be changed when the redirected request is made.

if ($outdated = 1) {
    return 307 https://www.cherryshoetech.com/;
}

The package.json browserslist was updated to remove support for any version of IE.  You can test that by running "npx browserslist".

"not ie > 0"

Again, I recommend you NOT put in the redirect code until you are certain the http_user_agent and outdated variables are working as expected.  If you do, while testing during development, you will need to clear browsing data, cache, etc, as redirect headers can be cached and you may think you are getting the latest Nginx change (but you may not be).  For Internet Explorer, to turn off temporary redirect HTTP 307 while experimenting, make sure to:

Select Tools (via the Gear Icon) > Safety > Delete browsing history.... ->Make sure to uncheck Preserve Favorites website data and check both Temporary Internet Files and Cookies.

Helpful articles:

https://gist.github.com/ddre54/10996786

https://serverfault.com/a/580739

https://answers.microsoft.com/en-us/ie/forum/ie11-windows_7/redirect-loop-in-internet-explorer-11-on-this-site/10dd261a-0359-49c9-aeb4-bfed8d01ead5


Sunday, March 14, 2021

CSS - divs side by side flexbox examples

Below are examples with <div>'s side by side styled with flexbox:

  1. two <div>'s with equal widths
  2. two <div>'s one with fixed width and other expanding width
  3. three <div>'s with defined percentage widths


cherryshoe.html

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="cherryshoe.css">
<title>cherryshoe</title>
</head>
<body>
    <h3>Flexbox - divs side by side</h3>
    <p>Example 1: equal widths</p>
    <div class="container-equal">
        <div class="container-equal-child">Left 50%</div>
        <div class="container-equal-child">Right 50%</div>
    </div>
    <p>Example 2: left taking up remaining, right fixed with</p>
    <div class="container-different">
        <div class="container-different-left">Left will expand to fill space</div>
        <div class="container-different-right">Right fixed width</div>
    </div>
    <p>Example 3: with defined percentage widths</p>
    <div class="container-thirds">
        <div class="container-thirds container-thirds-child-left">Left with 20%</div>
        <div class="container-thirds container-thirds-child-center">Center with 30%</div>
        <div class="container-thirds container-thirds-child-right">Right with 50%</div>
    </div>
</body>
</html>

cherryshoe.css

.container-equal {
  display: flex; /* define flex container with default row direction */
}
.container-equal-child{
  flex: 1;  /* grow */
  background: orange;
}
.container-equal-child:first-child{
  flex: 1;  /* grow */
  background: yellow;
 }

.container-different {
  display: flex;
}
.container-different-left{
  flex: 1;  /* grow */
  background: yellow;
}
.container-different-right {
  flex: 0 0 250px; /* flex-grow 0, flex-shrink 0, flex-basis with fixed width */
  background: orange;
}

.container-thirds {
  display: flex;
  width: 100%; 
  background: yellow;
}
.container-thirds .container-thirds-child-left {
  width: 20%; 
}
.container-thirds .container-thirds-child-center {
  width: 30%; 
  background: orange;
}
.container-thirds .container-thirds-child-right {
  width: 50%; 
  background: pink;
}

Flexbox is supported in modern browsers:



Saturday, February 13, 2021

React tether-component - get access to renderTarget element

Problem

The renderTarget (anchor) is a div that displays a default color.  When the renderTarget is clicked, it opens up the renderElement, which is a react-color component.  

I need to be able to choose a new color with react-color and then display it on the anchor (renderTarget).  

Environment:

CentOS Linux release 7.3.1611 (Core)

React 17.1

react-tether 2.0.7

react-color 2.19.3

Solution

I wasn't able to find a way to get access to either the renderTarget or renderElement directly.  The references to those are internal to the <TetherComponent>.  To gain access to the renderTarget element, I added a React ref in the constructor to the <TetherComponent>:

this.tetherComponentRef 

and assigned it when defining the <TetherComponent>, and accessed with:

this.tetherComponentRef._tetherInstance.target

The full solution is here:

import React from "react";
import { CompactPicker } from "react-color";
import TetherComponent from "react-tether";

const defaultOptions = {
  attachment: "top left",
  targetAttachment: "top left"
};

class CherryShoeColorPicker extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      open: false
    };
    this.handleChange = this.handleChange.bind(this);
    this.handleClick = this.handleClick.bind(this);

    this.tetherComponentRef = React.createRef();
  }

  /**
   * Handler when the CompactPicker color is changed
* @param {Object} colorObject */ handleChange(colorObject) { const { open } = this.state; this.tetherComponentRef._tetherInstance.target.style.fill = colorObject.hex; this.setState({ open: !open }); } /** * Handler when the anchor element is clicked */ handleClick() { const { open } = this.state; this.setState({ open: !open }); } render() { const { color, tetherOptions } = this.props; const { open } = this.state; const tetherOptions = tetherOptions ? { ...defaultOptions, ...tetherOptions } : defaultOptions; return ( <div> <TetherComponent {...tetherOptions} ref={tether => (this.tetherComponentRef = tether)} // renderTarget: This is what the item will be tethered to, make sure to attach the ref renderTarget={ref => ( <div ref={ref} onClick={this.handleClick} style={{ backgroundColor: color, height: "10px", width: "10px" }} ></div> )} // renderElement: this item will be tethered to the the component returned by renderTarget renderElement={ref => open && ( <CompactPicker ref={ref} color={color} onChangeComplete={this.handleChange} /> ) } /> </div> ); } } CherryShoeColorPicker.defaultProps = { tetherOptions: {} }; export default CherryShoeColorPicker;

You can also pass in JSX as props for use in either the renderTarget or renderElement.  The key thing is that you need to pass the ref argument into the renderTarget/renderElement function, and that the ref needs to be attached to the highest element in the DOM tree.  The highest element must already be defined when defining <TetherComponent>, it cannot be passed in as JSX from a prop.

These articles were helpful:

https://www.npmjs.com/package/react-tether

https://reactjs.org/docs/refs-and-the-dom.html

https://github.com/danreeves/react-tether/issues/1

Sunday, January 17, 2021

Node.js exec with promisify

I'm working on a project where Nodejs is used for the backend and redis is used for caching application data.  Here's an example of making a shell command to delete redis cache keys using child_process exec and promisify.  I find it easier (and more intuitive) to use async/await than to use callbacks.

Environment:
CentOS Linux release 7.3.1611 (Core)
node v14.9.0

// child process currently doesn't natively support promises, however can wrap the
// methods with promises using built-in promisify util
const { promisify } = require("util");
const execAsync = promisify(require("child_process").exec);

/**
 * Deletes all keys from cache that start with cacheCode param.  If
 * cacheCode is not defined, deletes all cache.
 * @param {String} cacheCode.
 * @throws {Error}
 * @returns {String} how many keys effected
 */
async function flushFromCache(cacheCode) {
  const pattern = cacheCode ? ` --pattern ${cacheCode}_*` : "";
  const command = `redis-cli --scan${pattern} | xargs redis-cli del`;
  try {
    const execResult = await execAsync(command);
    // result looks like { stdout: string, stderr: string }.
    // stdout is populated when everything goes nicely.
    // stderr is populated if error occurred while running command
    if (execResult.stderr) {
      throw new Error(execResult.stderr);
    }

    // When there are keys deleted the result looks like:
    // (integer) 2
    // When there are no keys found the result looks like:
    // ERR wrong number of arguments for 'del' command
    let result;
    if (execResult.stdout.includes("ERR")) {
      result = `No ${cacheCode ? `'${cacheCode}'` : ""} keys to flush`;
    } else {
      result = `${execResult.stdout.trim()} ${
        cacheCode ? `'${cacheCode}'` : ""
      } keys flushed`;
    }
    return result;
  } catch (e) {
    // this error can occur if the command is bad
    throw new Error(e.message);
  }
}

module.exports = {
  flushFromCache
};

These articles were helpful:
https://stackabuse.com/executing-shell-commands-with-node-js/ https://stackoverflow.com/questions/20643470/execute-a-command-line-binary-with-node-js

Friday, December 4, 2020

Docker Compose - change COMPOSE_HTTP_TIMEOUT and verify env_file environment variable configuration options

I occasionally get the below error while running docker-compose on my local VM or while running on Jenkins.

ERROR: for cherryshoe_test_ta_run_609fc9bdd396  UnixHTTPConnectionPool(host='localhost', port=None): Read timed out. (read timeout=60)
An HTTP request took too long to complete. Retry with --verbose to obtain debug information.
If you encounter this issue regularly because of slow network conditions, consider setting COMPOSE_HTTP_TIMEOUT to a higher value (current value: 60).

Environment:
CentOS Linux release 7.3.1611 (Core)
Docker version 19.03.1, build 74b1e89

You can verify this environment variable with the docker-compose config command, which prints the resolved application config to the terminal, by accessing the environment variable in the docker-compose file.   A sample docker-compose yml below is used to test this out:

docker-compose-cs.yml
version: "3"
services:
  cherryshoe:
    image: "testconfig:${COMPOSE_HTTP_TIMEOUT}"

The config command verifies the default COMPOSE_HTTP_TIMEOUT value of 60:

$ docker-compose -f docker-compose-cs.yml config
services:
  cherryshoe:
    image: testconfig:60
version: '3.0'

After using the env_file to set the value to a higher number,

.env

#.env file for docker-compose.  Use same file for all environments.
# set COMPOSE_HTTP_TIMEOUT at docker instance level and not inside docker image.
COMPOSE_HTTP_TIMEOUT=120

run the config command again to verify it:

$ docker-compose -f docker-compose-cs.yml config
services:
  cherryshoe:
    image: testconfig:120
version: '3.0'

This article was helpful.


Tuesday, November 10, 2020

Docker - enabling systemd on centos:7

I'm experimenting with enabling systemd on the docker centos:7 image.  Here's a simple example getting the centos7 systemd image set up, then building a docker image with nginx using that, and then running it.  

Environment:
CentOS Linux release 7.3.1611 (Core)
Docker version 19.03.1, build 74b1e89

centos7-systemd.Dockerfile

# systemd is included with centos:7 but not enabled by default - START
# https://hub.docker.com/_/centos:
FROM centos:7
ENV container docker
RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == \
systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
VOLUME [ "/sys/fs/cgroup" ]
CMD ["/usr/sbin/init"]
# systemd is included with centos:7 but not enabled by default - END

centos7-systemd-nginx.Dockerfile

# This dockerfile is used when running cypress during a commit to the remote repo
FROM local/centos7-systemd

# install yum dependencies ansible
RUN yum install -y epel-release \
  && yum install -y nginx && systemctl enable nginx.service

EXPOSE 80

CMD ["/usr/sbin/init"]

Build images:

docker build --rm -t local/centos7-systemd -f centos7-systemd.Dockerfile .
docker build -t local/centos7-systemd-nginx -f centos7-systemd-nginx.Dockerfile .

Run container:

docker run -it --rm -d -v /sys/fs/cgroup:/sys/fs/cgroup:ro -v /run -p 80:80 --name web local/centos7-systemd-nginx

Test that nginx is running: 

curl http://localhost/index.html

It returned an error HTML page, but I know nginx is running!

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
    <head>
        <title>The page is not found</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <style type="text/css">
            /*<![CDATA[*/
            body {
                background-color: #fff;
                color: #000;
                font-size: 0.9em;
                font-family: sans-serif,helvetica;
                margin: 0;
                padding: 0;
            }
            :link {
                color: #c00;
            }
            :visited {
                color: #c00;
            }
            a:hover {
                color: #f50;
            }
            h1 {
                text-align: center;
                margin: 0;
                padding: 0.6em 2em 0.4em;
                background-color: #294172;
                color: #fff;
                font-weight: normal;
                font-size: 1.75em;
                border-bottom: 2px solid #000;
            }
            h1 strong {
                font-weight: bold;
                font-size: 1.5em;
            }
            h2 {
                text-align: center;
                background-color: #3C6EB4;
                font-size: 1.1em;
                font-weight: bold;
                color: #fff;
                margin: 0;
                padding: 0.5em;
                border-bottom: 2px solid #294172;
            }
            h3 {
                text-align: center;
                background-color: #ff0000;
                padding: 0.5em;
                color: #fff;
            }
            hr {
                display: none;
            }
            .content {
                padding: 1em 5em;
            }
            .alert {
                border: 2px solid #000;
            }
            img {
                border: 2px solid #fff;
                padding: 2px;
                margin: 2px;
            }
            a:hover img {
                border: 2px solid #294172;
            }
            .logos {
                margin: 1em;
                text-align: center;
            }
            /*]]>*/
        </style>
    </head>
    
    <body>
        <h1><strong>nginx error!</strong></h1>
        <div class="content">
            <h3>The page you are looking for is not found.</h3>
            <div class="alert">
                <h2>Website Administrator</h2>
                <div class="content">
                    <p>Something has triggered missing webpage on your
                    website. This is the default 404 error page for
                    <strong>nginx</strong> that is distributed with
                    Fedora.  It is located
                    <tt>/usr/share/nginx/html/404.html</tt></p>

                    <p>You should customize this error page for your own
                    site or edit the <tt>error_page</tt> directive in
                    the <strong>nginx</strong> configuration file
                    <tt>/etc/nginx/nginx.conf</tt>.</p>

                </div>
            </div>

            <div class="logos">
                <a href="http://nginx.net/"><img
                    src="/nginx-logo.png"
                    alt="[ Powered by nginx ]"
                    width="121" height="32" /></a>

                <a href="http://fedoraproject.org/"><img
                    src="/poweredby.png"
                    alt="[ Powered by Fedora ]"
                    width="88" height="31" /></a>
            </div>
        </div>
    </body>
</html>

These articles were helpful:
https://hub.docker.com/_/centos
https://stackoverflow.com/questions/36617368/docker-centos-7-with-systemctl-failed-to-mount-tmpfs-cgroup